diff --git a/.circleci/config.yml b/.circleci/config.yml index 04ff4f9756..fbb443579d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ templates: golang-template: &golang-template docker: - - image: uroottest/test-image-amd64:v5.2.0 + - image: cimg/go:1.21 working_directory: /home/circleci/go/src/github.com/u-root/u-root environment: - UROOT_SOURCE: /home/circleci/go/src/github.com/u-root/u-root diff --git a/.circleci/images/builduploadall.sh b/.circleci/images/builduploadall.sh index 6e7a7dfb31..41543367c3 100755 --- a/.circleci/images/builduploadall.sh +++ b/.circleci/images/builduploadall.sh @@ -10,14 +10,6 @@ if [[ $VC != "3" ]]; then exit 1 fi -for GOARCH in amd64 arm arm64; do - ( - cd test-image-$GOARCH - docker build . -t uroottest/test-image-$GOARCH:$VERSION - docker push uroottest/test-image-$GOARCH:$VERSION - ) -done - # Tamago has slightly different requirements; until we are sure why, # do a slightly custom build diff --git a/.circleci/images/kernel-amd64/Dockerfile b/.circleci/images/kernel-amd64/Dockerfile new file mode 100644 index 0000000000..0974e21e50 --- /dev/null +++ b/.circleci/images/kernel-amd64/Dockerfile @@ -0,0 +1,35 @@ +# Copyright 2018-2021 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + `# Linux dependencies` \ + bc \ + bison \ + flex \ + gcc \ + git \ + make \ + `# Linux kernel build deps` \ + libelf-dev; + +# Create working directory +WORKDIR /root + +# Build linux +RUN git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; + +COPY config_linux.txt linux/.config +RUN chmod 0444 linux/.config; + +RUN cd linux; \ + make olddefconfig; \ + make -j$(($(nproc) * 2 + 1)); + +FROM scratch +COPY --from=base /root/linux/arch/x86_64/boot/bzImage /bzImage diff --git a/.circleci/images/test-image-amd64/config_linux.txt b/.circleci/images/kernel-amd64/config_linux.txt similarity index 92% rename from .circleci/images/test-image-amd64/config_linux.txt rename to .circleci/images/kernel-amd64/config_linux.txt index 1e3cd3ca17..aeb12e553f 100644 --- a/.circleci/images/test-image-amd64/config_linux.txt +++ b/.circleci/images/kernel-amd64/config_linux.txt @@ -22,6 +22,10 @@ CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_TTY=y +# Always print to serial port +CONFIG_CMDLINE_BOOL=y +CONFIG_CMDLINE="console=ttyS0 earlyprintk=ttyS0" + # Block devices CONFIG_BLOCK=y CONFIG_ATA=y @@ -74,6 +78,7 @@ CONFIG_CRYPTO_DEV_VIRTIO=y CONFIG_VIRTIO_BLK=y CONFIG_VIRTIO_SCSI=y CONFIG_VIRTIO_NET=y +CONFIG_VIRTIO_CONSOLE=y # Networking CONFIG_NET=y @@ -126,3 +131,8 @@ CONFIG_ACPI=y CONFIG_ACPI_FPDT=y CONFIG_DEVMEM=y CONFIG_STRICT_DEVMEM=n + +# Debugging +CONFIG_DEBUG_FS=y +CONFIG_GCOV_KERNEL=y +CONFIG_GCOV_PROFILE_ALL=y diff --git a/.circleci/images/kernel-arm/Dockerfile b/.circleci/images/kernel-arm/Dockerfile new file mode 100644 index 0000000000..303e4183a2 --- /dev/null +++ b/.circleci/images/kernel-arm/Dockerfile @@ -0,0 +1,38 @@ +# Copyright 2018-2023 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + `# Linux dependencies` \ + bc \ + bison \ + flex \ + gcc \ + git \ + make \ + gcc-arm-linux-gnueabi \ + `# Linux kernel build deps` \ + libelf-dev; + +WORKDIR /root + +# Build linux +RUN git clone --depth=1 --branch=v6.1.68 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git + +# Copy config file +COPY config_linux.txt linux/.config +RUN chmod 0444 linux/.config + +RUN cd linux; \ + export ARCH=arm; \ + export CROSS_COMPILE=arm-linux-gnueabi-; \ + make olddefconfig; \ + make -j$(($(nproc) * 2 + 1)); + +FROM scratch +COPY --from=base /root/linux/arch/arm/boot/zImage /zImage diff --git a/.circleci/images/kernel-arm/config_linux.txt b/.circleci/images/kernel-arm/config_linux.txt new file mode 100644 index 0000000000..034da7259c --- /dev/null +++ b/.circleci/images/kernel-arm/config_linux.txt @@ -0,0 +1,141 @@ +# If you copy this to your Linux directory, run +# make olddefconfig +# to fill in the blanks. + +CONFIG_ARCH_MULTIPLATFORM=y +CONFIG_ARCH_MULTI_V7=y +CONFIG_ARCH_MULTI_V6_V7=y +CONFIG_ARCH_VIRT=y +CONFIG_RETPOLINE=n + +# CONFIG_ACPI_CUSTOM_DSDT_FILE="" +# CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y + +# Minimal kernel config needed for Go and serial port: +CONFIG_BINFMT_ELF=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_DEVTMPFS=y +CONFIG_TMPFS=y +CONFIG_EPOLL=y +CONFIG_FUTEX=y +CONFIG_PRINTK=y +CONFIG_PROC_FS=y +CONFIG_TTY=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y + +# Always print to serial +CONFIG_CMDLINE_BOOL=y +CONFIG_CMDLINE="console=ttyAMA0" + +# Add /dev/mem for io command: +CONFIG_DEVMEM=y +CONFIG_STRICT_DEVMEM=n + +# vfat filesystem: +CONFIG_BLOCK=y +CONFIG_ATA=y +CONFIG_SATA_AHCI=y +CONFIG_BLK_DEV_NVME=y +CONFIG_BLK_DEV_SD=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_NLS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_MSDOS_FS=y +CONFIG_EXT4_FS=y + +# 9P filesystem +CONFIG_NET_9P=y +CONFIG_NET_9P_VIRTIO=y +CONFIG_9P_FS=y +# virtio-9p-device on qemu-system-arm uses virtio MMIO. +CONFIG_VIRTIO_MMIO=y + +CONFIG_PCI=y +CONFIG_VIRTIO_PCI=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCI_IOV=y +CONFIG_HOTPLUG_PCI=y +CONFIG_HOTPLUG_PCI_ACPI=y +CONFIG_PCI_AARDVARK=y +CONFIG_PCI_TEGRA=y +CONFIG_PCIE_RCAR=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCI_XGENE=y +CONFIG_PCIE_ALTERA=y +CONFIG_PCIE_ALTERA_MSI=y +CONFIG_PCI_HOST_THUNDER_PEM=y +CONFIG_PCI_HOST_THUNDER_ECAM=y +CONFIG_PCIE_ROCKCHIP_HOST=m +CONFIG_PCI_LAYERSCAPE=y +CONFIG_PCI_HISI=y +CONFIG_PCIE_QCOM=y +CONFIG_PCIE_ARMADA_8K=y +CONFIG_PCIE_KIRIN=y +CONFIG_PCIE_HISI_STB=y + +# Loop device for tcz: + +CONFIG_BLK_DEV_LOOP=y +CONFIG_MISC_FILESYSTEMS=y +CONFIG_SQUASHFS=y + +# Virtio Networking + random + storage +CONFIG_VIRTIO_PCI=y +CONFIG_HW_RANDOM_VIRTIO=y +CONFIG_CRYPTO_DEV_VIRTIO=y +CONFIG_VIRTIO_BLK=y +CONFIG_VIRTIO_SCSI=y +CONFIG_VIRTIO_NET=y +CONFIG_VIRTIO_CONSOLE=y + +# Networking +CONFIG_NET=y +CONFIG_INET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_IPV6=y +CONFIG_NET_CORE=y +CONFIG_NETDEVICES=y +CONFIG_ETHERNET=y +CONFIG_E1000=y + +# GPIO test - mock GPIO libraries +CONFIG_GPIOLIB=y +CONFIG_GPIO_MOCKUP=y +CONFIG_GPIO_SYSFS=y + +# For the kernel doing the kexec'ing +CONFIG_CRYPTO=y +CONFIG_KEXEC=y +CONFIG_KEXEC_FILE=y + +# For the kernel being kexec'ed +CONFIG_RELOCATABLE=y + +# Enable ACPI +CONFIG_ACPI=y + +# pkg/efivarfs (and its test) require immutable bit on xattr +CONFIG_TMPFS_XATTR=y + +# v6.0 has a missing dependency, and PCIE_KIRIN is "y" in the defconfig. +# Compilation fails if you use this and run `make olddefconfig` without setting +# CONFIG_PCIE_KIRIN=n explicitly. +CONFIG_PCIE_KIRIN=n + +# Enable time in guest. QEMU uses PL031 to set RTC. pkg/boot/fit requires +# current time signature checks. +CONFIG_RTC_CLASS=y +CONFIG_ARM_RZN1=y +CONFIG_RTC_DRV_PL031=y + +# Debugging +CONFIG_DEBUG_FS=y +CONFIG_GCOV_KERNEL=y +CONFIG_GCOV_PROFILE_ALL=y diff --git a/.circleci/images/kernel-arm64/Dockerfile b/.circleci/images/kernel-arm64/Dockerfile new file mode 100644 index 0000000000..b8c6978c93 --- /dev/null +++ b/.circleci/images/kernel-arm64/Dockerfile @@ -0,0 +1,38 @@ +# Copyright 2020-2023 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + `# Linux dependencies` \ + bc \ + bison \ + flex \ + gcc \ + git \ + make \ + gcc-aarch64-linux-gnu \ + libssl-dev \ + `# Linux kernel build deps` \ + libelf-dev; + +WORKDIR /root + +# Build linux +RUN git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; + +COPY config_linux.txt linux/.config +RUN chmod 0444 linux/.config; + +RUN cd linux; \ + export ARCH=arm64; \ + export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-; \ + make olddefconfig; \ + make -j$(($(nproc) * 2 + 1)); + +FROM scratch +COPY --from=base /root/linux/arch/arm64/boot/Image /Image diff --git a/.circleci/images/test-image-arm64/config_linux.txt b/.circleci/images/kernel-arm64/config_linux.txt similarity index 96% rename from .circleci/images/test-image-arm64/config_linux.txt rename to .circleci/images/kernel-arm64/config_linux.txt index 9edbf80085..9b4a8342cb 100644 --- a/.circleci/images/test-image-arm64/config_linux.txt +++ b/.circleci/images/kernel-arm64/config_linux.txt @@ -86,6 +86,7 @@ CONFIG_CRYPTO_DEV_VIRTIO=y CONFIG_VIRTIO_BLK=y CONFIG_VIRTIO_SCSI=y CONFIG_VIRTIO_NET=y +CONFIG_VIRTIO_CONSOLE=y # Networking CONFIG_NET=y @@ -127,3 +128,8 @@ CONFIG_PCIE_KIRIN=n CONFIG_RTC_CLASS=y CONFIG_ARM_RZN1=y CONFIG_RTC_DRV_PL031=y + +# Debugging +CONFIG_DEBUG_FS=y +CONFIG_GCOV_KERNEL=y +CONFIG_GCOV_PROFILE_ALL=y diff --git a/.circleci/images/multiboot-test-kernel-amd64/Dockerfile b/.circleci/images/multiboot-test-kernel-amd64/Dockerfile new file mode 100644 index 0000000000..9e594aaa36 --- /dev/null +++ b/.circleci/images/multiboot-test-kernel-amd64/Dockerfile @@ -0,0 +1,28 @@ +# Copyright 2018-2021 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling as base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + make \ + gcc-multilib \ + gzip \ + gcc; + +# Create working directory +WORKDIR /root + +# Build Multiboot kernel +RUN git clone --depth=1 https://github.com/u-root/multiboot-test-kernel; +RUN cd multiboot-test-kernel; \ + git checkout f5d529c940603ea9398a488f910142529387e4a6; \ + make; + +FROM scratch +COPY --from=base /root/multiboot-test-kernel/kernel ./mb/kernel +COPY --from=base /root/multiboot-test-kernel/kernel.gz ./mb/kernel.gz diff --git a/.circleci/images/test-image-amd64/Dockerfile b/.circleci/images/test-image-amd64/Dockerfile deleted file mode 100644 index 8682fa5695..0000000000 --- a/.circleci/images/test-image-amd64/Dockerfile +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2018-2021 the u-root Authors. All rights reserved -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -FROM cimg/go:1.21 - -# Install dependencies -RUN sudo apt-get update && \ - sudo apt-get install -y --no-install-recommends \ - `# Linux dependencies` \ - bc \ - bison \ - flex \ - gcc \ - git \ - make \ - `# QEMU dependencies` \ - libattr1-dev \ - libcap-dev \ - libcap-ng-dev \ - libfdt-dev \ - libglib2.0-dev \ - libpixman-1-dev \ - meson \ - ninja-build \ - python3 \ - zlib1g-dev \ - `# Linux kernel build deps` \ - libelf-dev \ - `# Multiboot kernel build deps` \ - gcc-multilib \ - gzip \ - `# Edk2 build deps` \ - uuid-dev \ - nasm \ - bash \ - iasl && \ - sudo rm -rf /var/lib/apt/lists/* - -# Create working directory -WORKDIR /home/circleci -COPY config_linux.txt .config - -# Build linux -RUN set -eux; \ - git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; \ - sudo chmod 0444 .config; \ - mv .config linux/; \ - cd linux; \ - make olddefconfig; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp linux/arch/x86_64/boot/bzImage bzImage; \ - rm -rf linux/ - -# Build QEMU -RUN set -eux; \ - git clone --depth=1 --branch=v7.0.0 https://github.com/qemu/qemu; \ - cd qemu; \ - mkdir build; \ - cd build; \ - ../configure \ - --target-list=x86_64-softmmu \ - --enable-virtfs \ - --disable-docs \ - --disable-sdl \ - --disable-kvm; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp -rL qemu/build/pc-bios/ ~/pc-bios; \ - cp qemu/build/x86_64-softmmu/qemu-system-x86_64 .; \ - rm -rf qemu/ - -# Build Multiboot kernel -RUN set -eux; \ - git clone --depth=1 \ - https://github.com/u-root/multiboot-test-kernel; \ - cd multiboot-test-kernel; \ - git checkout 1c7e4f4722077dcab308cd1df9818eab011e58c4; \ - make; \ - cd ~; \ - cp multiboot-test-kernel/kernel.gz ./; \ - rm -rf multiboot-test-kernel/ - -SHELL ["/bin/bash", "-c"] -RUN set -ex; \ - git clone --branch uefipayload-2023 --recursive \ - https://github.com/linuxboot/edk2 uefipayload; \ - cd uefipayload; \ - source ./edksetup.sh; \ - make -C BaseTools; \ - build -a X64 -p UefiPayloadPkg/UefiPayloadPkg.dsc -b DEBUG \ - -t GCC5 -D BOOTLOADER=LINUXBOOT -D DISABLE_MMX_SSE=true; \ - cp Build/UefiPayloadPkgX64/DEBUG_GCC5/FV/UEFIPAYLOAD.fd ~/; \ - cd ~; \ - rm -rf uefipayload/ - -# Export paths to binaries. -ENV UROOT_KERNEL /home/circleci/bzImage -ENV UROOT_QEMU "/home/circleci/qemu-system-x86_64 -L /home/circleci/pc-bios -m 1G" -ENV UROOT_TESTARCH amd64 diff --git a/.circleci/images/test-image-arm/Dockerfile b/.circleci/images/test-image-arm/Dockerfile deleted file mode 100644 index 3153acae71..0000000000 --- a/.circleci/images/test-image-arm/Dockerfile +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2018-2021 the u-root Authors. All rights reserved -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -FROM cimg/go:1.21 - -# Install dependencies -RUN sudo apt-get update && \ - sudo apt-get install -y --no-install-recommends \ - `# Linux dependencies` \ - bc \ - bison \ - flex \ - gcc-arm-linux-gnueabi \ - git \ - make \ - `# QEMU dependencies` \ - libattr1-dev \ - libcap-dev \ - libcap-ng-dev \ - libfdt-dev \ - libglib2.0-dev \ - libpixman-1-dev \ - meson \ - ninja-build \ - python3 \ - zlib1g-dev \ - `# Linux kernel build deps` \ - libelf-dev && \ - sudo rm -rf /var/lib/apt/lists/* - -# Create working directory -WORKDIR /home/circleci -COPY config_linux5.10.0_arm.txt .config - -# Build linux -RUN set -eux; \ - git clone --depth=1 --branch=v5.10 https://github.com/torvalds/linux; \ - sudo chmod 0444 .config; \ - mv .config linux/; \ - cd linux; \ - export ARCH=arm; \ - export CROSS_COMPILE=/usr/bin/arm-linux-gnueabi-; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp linux/arch/arm/boot/zImage zImage; \ - rm -rf linux/ - -# Build QEMU -RUN set -eux; \ - git clone --depth=1 --branch=v7.0.0 https://github.com/qemu/qemu; \ - cd qemu; \ - mkdir build; \ - cd build; \ - ../configure \ - --target-list=arm-softmmu \ - --enable-virtfs \ - --disable-docs \ - --disable-sdl \ - --disable-kvm; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp -rL qemu/build/pc-bios/ ~/pc-bios; \ - cp qemu/build/arm-softmmu/qemu-system-arm .; \ - rm -rf qemu/ - -# Export paths to binaries. -ENV UROOT_KERNEL /home/circleci/zImage -ENV UROOT_QEMU "/home/circleci/qemu-system-arm -M virt -L /home/circleci/pc-bios" -ENV UROOT_TESTARCH arm diff --git a/.circleci/images/test-image-arm/config_linux5.10.0_arm.txt b/.circleci/images/test-image-arm/config_linux5.10.0_arm.txt deleted file mode 100644 index 7fda1b8223..0000000000 --- a/.circleci/images/test-image-arm/config_linux5.10.0_arm.txt +++ /dev/null @@ -1,2124 +0,0 @@ -# -# Automatically generated file; DO NOT EDIT. -# Linux/arm 5.2.0 Kernel Configuration -# - -# -# Compiler: arm-linux-gnueabi-gcc (Debian 8.3.0-6) 8.3.0 -# -CONFIG_CC_IS_GCC=y -CONFIG_GCC_VERSION=80300 -CONFIG_CLANG_VERSION=0 -CONFIG_CC_HAS_ASM_GOTO=y -CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y -CONFIG_CC_DISABLE_WARN_MAYBE_UNINITIALIZED=y -CONFIG_IRQ_WORK=y -CONFIG_BUILDTIME_EXTABLE_SORT=y - -# -# General setup -# -CONFIG_BROKEN_ON_SMP=y -CONFIG_INIT_ENV_ARG_LIMIT=32 -# CONFIG_COMPILE_TEST is not set -CONFIG_LOCALVERSION="" -# CONFIG_LOCALVERSION_AUTO is not set -CONFIG_BUILD_SALT="" -CONFIG_HAVE_KERNEL_GZIP=y -CONFIG_HAVE_KERNEL_LZMA=y -CONFIG_HAVE_KERNEL_XZ=y -CONFIG_HAVE_KERNEL_LZO=y -CONFIG_HAVE_KERNEL_LZ4=y -# CONFIG_KERNEL_GZIP is not set -# CONFIG_KERNEL_LZMA is not set -CONFIG_KERNEL_XZ=y -# CONFIG_KERNEL_LZO is not set -# CONFIG_KERNEL_LZ4 is not set -CONFIG_DEFAULT_HOSTNAME="(none)" -CONFIG_SWAP=y -# CONFIG_SYSVIPC is not set -# CONFIG_POSIX_MQUEUE is not set -CONFIG_CROSS_MEMORY_ATTACH=y -# CONFIG_USELIB is not set -# CONFIG_AUDIT is not set -CONFIG_HAVE_ARCH_AUDITSYSCALL=y - -# -# IRQ subsystem -# -CONFIG_GENERIC_IRQ_PROBE=y -CONFIG_GENERIC_IRQ_SHOW=y -CONFIG_GENERIC_IRQ_SHOW_LEVEL=y -CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y -CONFIG_HARDIRQS_SW_RESEND=y -CONFIG_IRQ_DOMAIN=y -CONFIG_IRQ_DOMAIN_HIERARCHY=y -CONFIG_GENERIC_MSI_IRQ=y -CONFIG_GENERIC_MSI_IRQ_DOMAIN=y -CONFIG_HANDLE_DOMAIN_IRQ=y -CONFIG_IRQ_FORCED_THREADING=y -CONFIG_SPARSE_IRQ=y -# end of IRQ subsystem - -CONFIG_GENERIC_IRQ_MULTI_HANDLER=y -CONFIG_ARCH_CLOCKSOURCE_DATA=y -CONFIG_GENERIC_TIME_VSYSCALL=y -CONFIG_GENERIC_CLOCKEVENTS=y - -# -# Timers subsystem -# -CONFIG_HZ_PERIODIC=y -# CONFIG_NO_HZ_IDLE is not set -# CONFIG_NO_HZ is not set -# CONFIG_HIGH_RES_TIMERS is not set -# end of Timers subsystem - -CONFIG_PREEMPT_NONE=y -# CONFIG_PREEMPT_VOLUNTARY is not set -# CONFIG_PREEMPT is not set - -# -# CPU/Task time and stats accounting -# -CONFIG_TICK_CPU_ACCOUNTING=y -# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set -# CONFIG_IRQ_TIME_ACCOUNTING is not set -# CONFIG_PSI is not set -# end of CPU/Task time and stats accounting - -# -# RCU Subsystem -# -CONFIG_TINY_RCU=y -# CONFIG_RCU_EXPERT is not set -CONFIG_SRCU=y -CONFIG_TINY_SRCU=y -# end of RCU Subsystem - -# CONFIG_IKCONFIG is not set -CONFIG_LOG_BUF_SHIFT=17 -CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13 -CONFIG_GENERIC_SCHED_CLOCK=y - -# -# Scheduler features -# -# end of Scheduler features - -# CONFIG_CGROUPS is not set -# CONFIG_CHECKPOINT_RESTORE is not set -# CONFIG_SCHED_AUTOGROUP is not set -# CONFIG_RELAY is not set -CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" -CONFIG_RD_GZIP=y -CONFIG_RD_BZIP2=y -CONFIG_RD_LZMA=y -CONFIG_RD_XZ=y -CONFIG_RD_LZO=y -CONFIG_RD_LZ4=y -# CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE is not set -CONFIG_CC_OPTIMIZE_FOR_SIZE=y -CONFIG_SYSCTL=y -CONFIG_HAVE_UID16=y -CONFIG_BPF=y -CONFIG_EXPERT=y -# CONFIG_MULTIUSER is not set -# CONFIG_SGETMASK_SYSCALL is not set -# CONFIG_SYSFS_SYSCALL is not set -# CONFIG_SYSCTL_SYSCALL is not set -# CONFIG_FHANDLE is not set -# CONFIG_POSIX_TIMERS is not set -CONFIG_PRINTK=y -CONFIG_PRINTK_NMI=y -# CONFIG_BUG is not set -# CONFIG_BASE_FULL is not set -CONFIG_FUTEX=y -CONFIG_FUTEX_PI=y -CONFIG_EPOLL=y -# CONFIG_SIGNALFD is not set -# CONFIG_TIMERFD is not set -# CONFIG_EVENTFD is not set -CONFIG_SHMEM=y -# CONFIG_AIO is not set -CONFIG_IO_URING=y -# CONFIG_ADVISE_SYSCALLS is not set -# CONFIG_MEMBARRIER is not set -# CONFIG_KALLSYMS is not set -# CONFIG_BPF_SYSCALL is not set -# CONFIG_USERFAULTFD is not set -CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y -# CONFIG_RSEQ is not set -CONFIG_EMBEDDED=y -CONFIG_HAVE_PERF_EVENTS=y -CONFIG_PERF_USE_VMALLOC=y -# CONFIG_PC104 is not set - -# -# Kernel Performance Events And Counters -# -# CONFIG_PERF_EVENTS is not set -# end of Kernel Performance Events And Counters - -# CONFIG_VM_EVENT_COUNTERS is not set -# CONFIG_COMPAT_BRK is not set -# CONFIG_SLAB is not set -# CONFIG_SLUB is not set -CONFIG_SLOB=y -# CONFIG_SLAB_MERGE_DEFAULT is not set -# CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set -# CONFIG_PROFILING is not set -# end of General setup - -CONFIG_ARM=y -CONFIG_ARM_HAS_SG_CHAIN=y -CONFIG_SYS_SUPPORTS_APM_EMULATION=y -CONFIG_HAVE_PROC_CPU=y -CONFIG_STACKTRACE_SUPPORT=y -CONFIG_LOCKDEP_SUPPORT=y -CONFIG_TRACE_IRQFLAGS_SUPPORT=y -CONFIG_FIX_EARLYCON_MEM=y -CONFIG_GENERIC_HWEIGHT=y -CONFIG_GENERIC_CALIBRATE_DELAY=y -CONFIG_ARCH_SUPPORTS_UPROBES=y -CONFIG_ARM_PATCH_PHYS_VIRT=y -CONFIG_PGTABLE_LEVELS=2 - -# -# System Type -# -CONFIG_MMU=y -CONFIG_ARCH_MMAP_RND_BITS_MIN=8 -CONFIG_ARCH_MMAP_RND_BITS_MAX=16 -CONFIG_ARCH_MULTIPLATFORM=y -# CONFIG_ARCH_EBSA110 is not set -# CONFIG_ARCH_EP93XX is not set -# CONFIG_ARCH_FOOTBRIDGE is not set -# CONFIG_ARCH_NETX is not set -# CONFIG_ARCH_IOP13XX is not set -# CONFIG_ARCH_IOP32X is not set -# CONFIG_ARCH_IOP33X is not set -# CONFIG_ARCH_IXP4XX is not set -# CONFIG_ARCH_DOVE is not set -# CONFIG_ARCH_KS8695 is not set -# CONFIG_ARCH_W90X900 is not set -# CONFIG_ARCH_LPC32XX is not set -# CONFIG_ARCH_PXA is not set -# CONFIG_ARCH_RPC is not set -# CONFIG_ARCH_SA1100 is not set -# CONFIG_ARCH_S3C24XX is not set -# CONFIG_ARCH_DAVINCI is not set -# CONFIG_ARCH_OMAP1 is not set - -# -# Multiple platform selection -# - -# -# CPU Core family selection -# -# CONFIG_ARCH_MULTI_V6 is not set -CONFIG_ARCH_MULTI_V7=y -CONFIG_ARCH_MULTI_V6_V7=y -# end of Multiple platform selection - -CONFIG_ARCH_VIRT=y -# CONFIG_ARCH_ACTIONS is not set -# CONFIG_ARCH_ALPINE is not set -# CONFIG_ARCH_ARTPEC is not set -# CONFIG_ARCH_AT91 is not set -# CONFIG_ARCH_BCM is not set -# CONFIG_ARCH_BERLIN is not set -# CONFIG_ARCH_DIGICOLOR is not set -# CONFIG_ARCH_EXYNOS is not set -# CONFIG_ARCH_HIGHBANK is not set -# CONFIG_ARCH_HISI is not set -# CONFIG_ARCH_MXC is not set -# CONFIG_ARCH_KEYSTONE is not set -# CONFIG_ARCH_MEDIATEK is not set -# CONFIG_ARCH_MESON is not set -# CONFIG_ARCH_MILBEAUT is not set -# CONFIG_ARCH_MMP is not set -# CONFIG_ARCH_MVEBU is not set -# CONFIG_ARCH_NPCM is not set - -# -# TI OMAP/AM/DM/DRA Family -# -# CONFIG_ARCH_OMAP3 is not set -# CONFIG_ARCH_OMAP4 is not set -# CONFIG_SOC_OMAP5 is not set -# CONFIG_SOC_AM33XX is not set -# CONFIG_SOC_AM43XX is not set -# CONFIG_SOC_DRA7XX is not set -# end of TI OMAP/AM/DM/DRA Family - -# CONFIG_ARCH_SIRF is not set -# CONFIG_ARCH_QCOM is not set -# CONFIG_ARCH_RDA is not set -# CONFIG_ARCH_REALVIEW is not set -# CONFIG_ARCH_ROCKCHIP is not set -# CONFIG_ARCH_S5PV210 is not set -# CONFIG_ARCH_RENESAS is not set -# CONFIG_ARCH_SOCFPGA is not set -# CONFIG_PLAT_SPEAR is not set -# CONFIG_ARCH_STI is not set -# CONFIG_ARCH_STM32 is not set -# CONFIG_ARCH_SUNXI is not set -# CONFIG_ARCH_TANGO is not set -# CONFIG_ARCH_TEGRA is not set -# CONFIG_ARCH_UNIPHIER is not set -# CONFIG_ARCH_U8500 is not set -# CONFIG_ARCH_VEXPRESS is not set -# CONFIG_ARCH_WM8850 is not set -# CONFIG_ARCH_ZX is not set -# CONFIG_ARCH_ZYNQ is not set - -# -# Processor Type -# -CONFIG_CPU_V7=y -CONFIG_CPU_THUMB_CAPABLE=y -CONFIG_CPU_32v6K=y -CONFIG_CPU_32v7=y -CONFIG_CPU_ABRT_EV7=y -CONFIG_CPU_PABRT_V7=y -CONFIG_CPU_CACHE_V7=y -CONFIG_CPU_CACHE_VIPT=y -CONFIG_CPU_COPY_V6=y -CONFIG_CPU_TLB_V7=y -CONFIG_CPU_HAS_ASID=y -CONFIG_CPU_CP15=y -CONFIG_CPU_CP15_MMU=y - -# -# Processor Features -# -# CONFIG_ARM_LPAE is not set -CONFIG_ARM_THUMB=y -# CONFIG_ARM_THUMBEE is not set -CONFIG_ARM_VIRT_EXT=y -# CONFIG_SWP_EMULATE is not set -# CONFIG_CPU_BIG_ENDIAN is not set -# CONFIG_CPU_ICACHE_DISABLE is not set -# CONFIG_CPU_DCACHE_DISABLE is not set -# CONFIG_CPU_BPREDICT_DISABLE is not set -CONFIG_CPU_SPECTRE=y -CONFIG_HARDEN_BRANCH_PREDICTOR=y -CONFIG_KUSER_HELPERS=y -CONFIG_VDSO=y -CONFIG_OUTER_CACHE=y -CONFIG_OUTER_CACHE_SYNC=y -CONFIG_MIGHT_HAVE_CACHE_L2X0=y -CONFIG_CACHE_L2X0=y -# CONFIG_PL310_ERRATA_588369 is not set -# CONFIG_PL310_ERRATA_727915 is not set -# CONFIG_PL310_ERRATA_753970 is not set -# CONFIG_PL310_ERRATA_769419 is not set -CONFIG_ARM_L1_CACHE_SHIFT_6=y -CONFIG_ARM_L1_CACHE_SHIFT=6 -CONFIG_ARM_DMA_MEM_BUFFERABLE=y -CONFIG_ARM_HEAVY_MB=y -CONFIG_ARCH_SUPPORTS_BIG_ENDIAN=y -CONFIG_DEBUG_ALIGN_RODATA=y -# CONFIG_ARM_ERRATA_430973 is not set -# CONFIG_ARM_ERRATA_720789 is not set -# CONFIG_ARM_ERRATA_754322 is not set -# CONFIG_ARM_ERRATA_775420 is not set -# CONFIG_ARM_ERRATA_773022 is not set -# CONFIG_ARM_ERRATA_818325_852422 is not set -# CONFIG_ARM_ERRATA_821420 is not set -# CONFIG_ARM_ERRATA_825619 is not set -# CONFIG_ARM_ERRATA_857271 is not set -# CONFIG_ARM_ERRATA_852421 is not set -# CONFIG_ARM_ERRATA_852423 is not set -# CONFIG_ARM_ERRATA_857272 is not set -# end of System Type - -# -# Bus support -# -# CONFIG_ARM_ERRATA_814220 is not set -# end of Bus support - -# -# Kernel Features -# -CONFIG_HAVE_SMP=y -# CONFIG_SMP is not set -CONFIG_HAVE_ARM_ARCH_TIMER=y -CONFIG_VMSPLIT_3G=y -# CONFIG_VMSPLIT_3G_OPT is not set -# CONFIG_VMSPLIT_2G is not set -# CONFIG_VMSPLIT_1G is not set -CONFIG_PAGE_OFFSET=0xC0000000 -CONFIG_ARM_PSCI=y -CONFIG_ARCH_NR_GPIO=0 -CONFIG_HZ_FIXED=0 -CONFIG_HZ_100=y -# CONFIG_HZ_200 is not set -# CONFIG_HZ_250 is not set -# CONFIG_HZ_300 is not set -# CONFIG_HZ_500 is not set -# CONFIG_HZ_1000 is not set -CONFIG_HZ=100 -CONFIG_THUMB2_KERNEL=y -CONFIG_ARM_PATCH_IDIV=y -CONFIG_AEABI=y -CONFIG_HAVE_ARCH_PFN_VALID=y -# CONFIG_HIGHMEM is not set -CONFIG_CPU_SW_DOMAIN_PAN=y -CONFIG_ARCH_WANT_GENERAL_HUGETLB=y -CONFIG_FORCE_MAX_ZONEORDER=11 -CONFIG_ALIGNMENT_TRAP=y -# CONFIG_UACCESS_WITH_MEMCPY is not set -# CONFIG_SECCOMP is not set -# CONFIG_PARAVIRT is not set -# CONFIG_PARAVIRT_TIME_ACCOUNTING is not set -# CONFIG_XEN is not set -# end of Kernel Features - -# -# Boot options -# -CONFIG_USE_OF=y -# CONFIG_ATAGS is not set -CONFIG_ZBOOT_ROM_TEXT=0 -CONFIG_ZBOOT_ROM_BSS=0 -# CONFIG_ARM_APPENDED_DTB is not set -CONFIG_CMDLINE="" -# CONFIG_KEXEC is not set -# CONFIG_CRASH_DUMP is not set -CONFIG_AUTO_ZRELADDR=y -# CONFIG_EFI is not set -# end of Boot options - -# -# CPU Power Management -# - -# -# CPU Frequency scaling -# -# CONFIG_CPU_FREQ is not set -# end of CPU Frequency scaling - -# -# CPU Idle -# -# CONFIG_CPU_IDLE is not set -# end of CPU Idle -# end of CPU Power Management - -# -# Floating point emulation -# - -# -# At least one emulation must be selected -# -# CONFIG_VFP is not set -# end of Floating point emulation - -# -# Power management options -# -# CONFIG_SUSPEND is not set -# CONFIG_HIBERNATION is not set -# CONFIG_PM is not set -# CONFIG_APM_EMULATION is not set -CONFIG_ARCH_SUSPEND_POSSIBLE=y -CONFIG_ARM_CPU_SUSPEND=y -CONFIG_ARCH_HIBERNATION_POSSIBLE=y -# end of Power management options - -# -# Firmware Drivers -# -# CONFIG_FIRMWARE_MEMMAP is not set -# CONFIG_TRUSTED_FOUNDATIONS is not set -CONFIG_HAVE_ARM_SMCCC=y -CONFIG_ARM_PSCI_FW=y -# CONFIG_GOOGLE_FIRMWARE is not set - -# -# Tegra firmware driver -# -# end of Tegra firmware driver -# end of Firmware Drivers - -# CONFIG_ARM_CRYPTO is not set -# CONFIG_VIRTUALIZATION is not set - -# -# General architecture-dependent options -# -CONFIG_HAVE_OPROFILE=y -# CONFIG_JUMP_LABEL is not set -CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y -CONFIG_ARCH_USE_BUILTIN_BSWAP=y -CONFIG_HAVE_KPROBES=y -CONFIG_HAVE_KRETPROBES=y -CONFIG_HAVE_NMI=y -CONFIG_HAVE_ARCH_TRACEHOOK=y -CONFIG_HAVE_DMA_CONTIGUOUS=y -CONFIG_GENERIC_SMP_IDLE_THREAD=y -CONFIG_GENERIC_IDLE_POLL_SETUP=y -CONFIG_ARCH_HAS_FORTIFY_SOURCE=y -CONFIG_ARCH_HAS_KEEPINITRD=y -CONFIG_ARCH_HAS_SET_MEMORY=y -CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y -CONFIG_ARCH_32BIT_OFF_T=y -CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y -CONFIG_HAVE_RSEQ=y -CONFIG_HAVE_CLK=y -CONFIG_HAVE_PERF_REGS=y -CONFIG_HAVE_PERF_USER_STACK_DUMP=y -CONFIG_HAVE_ARCH_JUMP_LABEL=y -CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y -CONFIG_HAVE_ARCH_SECCOMP_FILTER=y -CONFIG_HAVE_STACKPROTECTOR=y -CONFIG_CC_HAS_STACKPROTECTOR_NONE=y -# CONFIG_STACKPROTECTOR is not set -CONFIG_HAVE_CONTEXT_TRACKING=y -CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y -CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y -CONFIG_HAVE_MOD_ARCH_SPECIFIC=y -CONFIG_MODULES_USE_ELF_REL=y -CONFIG_ARCH_HAS_ELF_RANDOMIZE=y -CONFIG_HAVE_ARCH_MMAP_RND_BITS=y -CONFIG_HAVE_EXIT_THREAD=y -CONFIG_ARCH_MMAP_RND_BITS=8 -CONFIG_CLONE_BACKWARDS=y -CONFIG_OLD_SIGSUSPEND3=y -CONFIG_OLD_SIGACTION=y -CONFIG_64BIT_TIME=y -CONFIG_COMPAT_32BIT_TIME=y -CONFIG_ARCH_OPTIONAL_KERNEL_RWX=y -CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT=y -CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y -CONFIG_STRICT_KERNEL_RWX=y -CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y -CONFIG_ARCH_HAS_PHYS_TO_DMA=y -CONFIG_REFCOUNT_FULL=y - -# -# GCOV-based kernel profiling -# -CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y -# end of GCOV-based kernel profiling - -CONFIG_PLUGIN_HOSTCC="" -CONFIG_HAVE_GCC_PLUGINS=y -# end of General architecture-dependent options - -CONFIG_RT_MUTEXES=y -CONFIG_BASE_SMALL=1 -# CONFIG_MODULES is not set -CONFIG_BLOCK=y -CONFIG_BLK_SCSI_REQUEST=y -CONFIG_BLK_DEV_BSG=y -# CONFIG_BLK_DEV_BSGLIB is not set -# CONFIG_BLK_DEV_INTEGRITY is not set -# CONFIG_BLK_DEV_ZONED is not set -# CONFIG_BLK_CMDLINE_PARSER is not set -# CONFIG_BLK_WBT is not set -# CONFIG_BLK_SED_OPAL is not set - -# -# Partition Types -# -# CONFIG_PARTITION_ADVANCED is not set -CONFIG_MSDOS_PARTITION=y -CONFIG_EFI_PARTITION=y -# end of Partition Types - -CONFIG_BLK_MQ_VIRTIO=y - -# -# IO Schedulers -# -CONFIG_MQ_IOSCHED_DEADLINE=y -CONFIG_MQ_IOSCHED_KYBER=y -# CONFIG_IOSCHED_BFQ is not set -# end of IO Schedulers - -CONFIG_INLINE_SPIN_UNLOCK_IRQ=y -CONFIG_INLINE_READ_UNLOCK=y -CONFIG_INLINE_READ_UNLOCK_IRQ=y -CONFIG_INLINE_WRITE_UNLOCK=y -CONFIG_INLINE_WRITE_UNLOCK_IRQ=y -CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y - -# -# Executable file formats -# -CONFIG_BINFMT_ELF=y -# CONFIG_BINFMT_ELF_FDPIC is not set -CONFIG_ELFCORE=y -# CONFIG_BINFMT_SCRIPT is not set -CONFIG_ARCH_HAS_BINFMT_FLAT=y -# CONFIG_BINFMT_FLAT is not set -CONFIG_BINFMT_FLAT_ARGVP_ENVP_ON_STACK=y -# CONFIG_BINFMT_MISC is not set -# CONFIG_COREDUMP is not set -# end of Executable file formats - -# -# Memory Management options -# -CONFIG_FLATMEM=y -CONFIG_FLAT_NODE_MEM_MAP=y -CONFIG_ARCH_KEEP_MEMBLOCK=y -CONFIG_SPLIT_PTLOCK_CPUS=4 -CONFIG_COMPACTION=y -CONFIG_MIGRATION=y -# CONFIG_KSM is not set -CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 -CONFIG_NEED_PER_CPU_KM=y -# CONFIG_CLEANCACHE is not set -# CONFIG_FRONTSWAP is not set -# CONFIG_CMA is not set -# CONFIG_ZPOOL is not set -# CONFIG_ZBUD is not set -# CONFIG_ZSMALLOC is not set -CONFIG_GENERIC_EARLY_IOREMAP=y -# CONFIG_PERCPU_STATS is not set -# CONFIG_GUP_BENCHMARK is not set -# end of Memory Management options - -CONFIG_NET=y - -# -# Networking options -# -# CONFIG_PACKET is not set -# CONFIG_UNIX is not set -# CONFIG_TLS is not set -# CONFIG_XFRM_USER is not set -# CONFIG_NET_KEY is not set -CONFIG_INET=y -# CONFIG_IP_MULTICAST is not set -# CONFIG_IP_ADVANCED_ROUTER is not set -# CONFIG_IP_PNP is not set -# CONFIG_NET_IPIP is not set -# CONFIG_NET_IPGRE_DEMUX is not set -CONFIG_NET_IP_TUNNEL=y -# CONFIG_SYN_COOKIES is not set -# CONFIG_NET_IPVTI is not set -# CONFIG_NET_FOU is not set -# CONFIG_NET_FOU_IP_TUNNELS is not set -# CONFIG_INET_AH is not set -# CONFIG_INET_ESP is not set -# CONFIG_INET_IPCOMP is not set -CONFIG_INET_TUNNEL=y -CONFIG_INET_DIAG=y -CONFIG_INET_TCP_DIAG=y -# CONFIG_INET_UDP_DIAG is not set -# CONFIG_INET_RAW_DIAG is not set -# CONFIG_INET_DIAG_DESTROY is not set -# CONFIG_TCP_CONG_ADVANCED is not set -CONFIG_TCP_CONG_CUBIC=y -CONFIG_DEFAULT_TCP_CONG="cubic" -# CONFIG_TCP_MD5SIG is not set -CONFIG_IPV6=y -# CONFIG_IPV6_ROUTER_PREF is not set -# CONFIG_IPV6_OPTIMISTIC_DAD is not set -# CONFIG_INET6_AH is not set -# CONFIG_INET6_ESP is not set -# CONFIG_INET6_IPCOMP is not set -# CONFIG_IPV6_MIP6 is not set -# CONFIG_IPV6_VTI is not set -CONFIG_IPV6_SIT=y -# CONFIG_IPV6_SIT_6RD is not set -CONFIG_IPV6_NDISC_NODETYPE=y -# CONFIG_IPV6_TUNNEL is not set -# CONFIG_IPV6_MULTIPLE_TABLES is not set -# CONFIG_IPV6_MROUTE is not set -# CONFIG_IPV6_SEG6_LWTUNNEL is not set -# CONFIG_IPV6_SEG6_HMAC is not set -# CONFIG_NETWORK_SECMARK is not set -# CONFIG_NETWORK_PHY_TIMESTAMPING is not set -# CONFIG_NETFILTER is not set -# CONFIG_BPFILTER is not set -# CONFIG_IP_DCCP is not set -# CONFIG_IP_SCTP is not set -# CONFIG_RDS is not set -# CONFIG_TIPC is not set -# CONFIG_ATM is not set -# CONFIG_L2TP is not set -# CONFIG_BRIDGE is not set -CONFIG_HAVE_NET_DSA=y -# CONFIG_NET_DSA is not set -# CONFIG_VLAN_8021Q is not set -# CONFIG_DECNET is not set -# CONFIG_LLC2 is not set -# CONFIG_ATALK is not set -# CONFIG_X25 is not set -# CONFIG_LAPB is not set -# CONFIG_PHONET is not set -# CONFIG_6LOWPAN is not set -# CONFIG_IEEE802154 is not set -# CONFIG_NET_SCHED is not set -# CONFIG_DCB is not set -# CONFIG_BATMAN_ADV is not set -# CONFIG_OPENVSWITCH is not set -# CONFIG_VSOCKETS is not set -# CONFIG_NETLINK_DIAG is not set -# CONFIG_MPLS is not set -# CONFIG_NET_NSH is not set -# CONFIG_HSR is not set -# CONFIG_NET_SWITCHDEV is not set -# CONFIG_NET_L3_MASTER_DEV is not set -# CONFIG_NET_NCSI is not set -CONFIG_NET_RX_BUSY_POLL=y - -# -# Network testing -# -# CONFIG_NET_PKTGEN is not set -# end of Network testing -# end of Networking options - -# CONFIG_HAMRADIO is not set -# CONFIG_CAN is not set -# CONFIG_BT is not set -# CONFIG_AF_RXRPC is not set -# CONFIG_AF_KCM is not set -CONFIG_WIRELESS=y -# CONFIG_CFG80211 is not set - -# -# CFG80211 needs to be enabled for MAC80211 -# -CONFIG_MAC80211_STA_HASH_MAX_SIZE=0 -# CONFIG_WIMAX is not set -# CONFIG_RFKILL is not set -CONFIG_NET_9P=y -CONFIG_NET_9P_VIRTIO=y -# CONFIG_NET_9P_DEBUG is not set -# CONFIG_CAIF is not set -# CONFIG_CEPH_LIB is not set -# CONFIG_NFC is not set -# CONFIG_PSAMPLE is not set -# CONFIG_NET_IFE is not set -# CONFIG_LWTUNNEL is not set -CONFIG_DST_CACHE=y -CONFIG_GRO_CELLS=y -CONFIG_FAILOVER=y -CONFIG_HAVE_EBPF_JIT=y - -# -# Device Drivers -# -CONFIG_ARM_AMBA=y -CONFIG_HAVE_PCI=y -# CONFIG_PCI is not set -# CONFIG_PCCARD is not set - -# -# Generic Driver Options -# -# CONFIG_UEVENT_HELPER is not set -CONFIG_DEVTMPFS=y -# CONFIG_DEVTMPFS_MOUNT is not set -# CONFIG_STANDALONE is not set -# CONFIG_PREVENT_FIRMWARE_BUILD is not set - -# -# Firmware loader -# -# CONFIG_FW_LOADER is not set -# end of Firmware loader - -# CONFIG_ALLOW_DEV_COREDUMP is not set -# CONFIG_DEBUG_DRIVER is not set -# CONFIG_DEBUG_DEVRES is not set -# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set -CONFIG_GENERIC_CPU_AUTOPROBE=y -# end of Generic Driver Options - -# -# Bus devices -# -# CONFIG_BRCMSTB_GISB_ARB is not set -# CONFIG_VEXPRESS_CONFIG is not set -# end of Bus devices - -# CONFIG_CONNECTOR is not set -# CONFIG_GNSS is not set -# CONFIG_MTD is not set -CONFIG_DTC=y -CONFIG_OF=y -# CONFIG_OF_UNITTEST is not set -CONFIG_OF_FLATTREE=y -CONFIG_OF_EARLY_FLATTREE=y -CONFIG_OF_ADDRESS=y -CONFIG_OF_IRQ=y -CONFIG_OF_NET=y -CONFIG_OF_RESERVED_MEM=y -# CONFIG_OF_OVERLAY is not set -CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y -# CONFIG_PARPORT is not set -CONFIG_BLK_DEV=y -# CONFIG_BLK_DEV_NULL_BLK is not set -CONFIG_BLK_DEV_LOOP=y -CONFIG_BLK_DEV_LOOP_MIN_COUNT=8 -# CONFIG_BLK_DEV_CRYPTOLOOP is not set -# CONFIG_BLK_DEV_DRBD is not set -# CONFIG_BLK_DEV_NBD is not set -# CONFIG_BLK_DEV_RAM is not set -# CONFIG_CDROM_PKTCDVD is not set -# CONFIG_ATA_OVER_ETH is not set -# CONFIG_VIRTIO_BLK is not set -# CONFIG_BLK_DEV_RBD is not set - -# -# NVME Support -# -# CONFIG_NVME_FC is not set -# end of NVME Support - -# -# Misc devices -# -# CONFIG_DUMMY_IRQ is not set -# CONFIG_ENCLOSURE_SERVICES is not set -# CONFIG_SRAM is not set -# CONFIG_PVPANIC is not set -# CONFIG_C2PORT is not set - -# -# EEPROM support -# -# CONFIG_EEPROM_93CX6 is not set -# end of EEPROM support - -# -# Texas Instruments shared transport line discipline -# -# end of Texas Instruments shared transport line discipline - -# -# Altera FPGA firmware download module (requires I2C) -# - -# -# Intel MIC & related support -# - -# -# Intel MIC Bus Driver -# - -# -# SCIF Bus Driver -# - -# -# VOP Bus Driver -# -# CONFIG_VOP_BUS is not set - -# -# Intel MIC Host Driver -# - -# -# Intel MIC Card Driver -# - -# -# SCIF Driver -# - -# -# Intel MIC Coprocessor State Management (COSM) Drivers -# - -# -# VOP Driver -# -# end of Intel MIC & related support - -# CONFIG_ECHO is not set -# end of Misc devices - -# -# SCSI device support -# -CONFIG_SCSI_MOD=y -# CONFIG_RAID_ATTRS is not set -CONFIG_SCSI=y -CONFIG_SCSI_DMA=y -CONFIG_SCSI_PROC_FS=y - -# -# SCSI support type (disk, tape, CD-ROM) -# -CONFIG_BLK_DEV_SD=y -# CONFIG_CHR_DEV_ST is not set -# CONFIG_CHR_DEV_OSST is not set -# CONFIG_BLK_DEV_SR is not set -# CONFIG_CHR_DEV_SG is not set -# CONFIG_CHR_DEV_SCH is not set -# CONFIG_SCSI_CONSTANTS is not set -# CONFIG_SCSI_LOGGING is not set -# CONFIG_SCSI_SCAN_ASYNC is not set - -# -# SCSI Transports -# -# CONFIG_SCSI_SPI_ATTRS is not set -# CONFIG_SCSI_FC_ATTRS is not set -# CONFIG_SCSI_ISCSI_ATTRS is not set -# CONFIG_SCSI_SAS_ATTRS is not set -# CONFIG_SCSI_SAS_LIBSAS is not set -# CONFIG_SCSI_SRP_ATTRS is not set -# end of SCSI Transports - -CONFIG_SCSI_LOWLEVEL=y -# CONFIG_ISCSI_TCP is not set -# CONFIG_ISCSI_BOOT_SYSFS is not set -# CONFIG_SCSI_UFSHCD is not set -# CONFIG_SCSI_DEBUG is not set -# CONFIG_SCSI_VIRTIO is not set -# CONFIG_SCSI_DH is not set -# end of SCSI device support - -CONFIG_ATA=y -CONFIG_ATA_VERBOSE_ERROR=y -CONFIG_SATA_PMP=y - -# -# Controllers with non-SFF native interface -# -# CONFIG_SATA_AHCI_PLATFORM is not set -# CONFIG_AHCI_CEVA is not set -# CONFIG_AHCI_QORIQ is not set -CONFIG_ATA_SFF=y - -# -# SFF controllers with custom DMA interface -# -CONFIG_ATA_BMDMA=y - -# -# SATA SFF controllers with BMDMA -# - -# -# PATA SFF controllers with BMDMA -# - -# -# PIO-only SFF controllers -# -# CONFIG_PATA_PLATFORM is not set - -# -# Generic fallback / legacy drivers -# -# CONFIG_MD is not set -# CONFIG_TARGET_CORE is not set -CONFIG_NETDEVICES=y -CONFIG_NET_CORE=y -# CONFIG_BONDING is not set -# CONFIG_DUMMY is not set -# CONFIG_EQUALIZER is not set -# CONFIG_NET_TEAM is not set -# CONFIG_MACVLAN is not set -# CONFIG_IPVLAN is not set -# CONFIG_VXLAN is not set -# CONFIG_GENEVE is not set -# CONFIG_GTP is not set -# CONFIG_MACSEC is not set -# CONFIG_NETCONSOLE is not set -# CONFIG_TUN is not set -# CONFIG_TUN_VNET_CROSS_LE is not set -# CONFIG_VETH is not set -CONFIG_VIRTIO_NET=y -# CONFIG_NLMON is not set - -# -# CAIF transport drivers -# - -# -# Distributed Switch Architecture drivers -# -# end of Distributed Switch Architecture drivers - -CONFIG_ETHERNET=y -CONFIG_NET_VENDOR_ALACRITECH=y -# CONFIG_ALTERA_TSE is not set -CONFIG_NET_VENDOR_AMAZON=y -CONFIG_NET_VENDOR_AQUANTIA=y -CONFIG_NET_VENDOR_ARC=y -CONFIG_NET_VENDOR_AURORA=y -# CONFIG_AURORA_NB8800 is not set -CONFIG_NET_VENDOR_BROADCOM=y -# CONFIG_B44 is not set -# CONFIG_BCMGENET is not set -# CONFIG_SYSTEMPORT is not set -CONFIG_NET_VENDOR_CADENCE=y -# CONFIG_MACB is not set -CONFIG_NET_VENDOR_CAVIUM=y -CONFIG_NET_VENDOR_CIRRUS=y -# CONFIG_CS89x0 is not set -CONFIG_NET_VENDOR_CORTINA=y -# CONFIG_GEMINI_ETHERNET is not set -# CONFIG_DM9000 is not set -# CONFIG_DNET is not set -CONFIG_NET_VENDOR_EZCHIP=y -# CONFIG_EZCHIP_NPS_MANAGEMENT_ENET is not set -CONFIG_NET_VENDOR_FARADAY=y -# CONFIG_FTMAC100 is not set -# CONFIG_FTGMAC100 is not set -CONFIG_NET_VENDOR_HISILICON=y -# CONFIG_HIX5HD2_GMAC is not set -# CONFIG_HISI_FEMAC is not set -# CONFIG_HIP04_ETH is not set -# CONFIG_HNS is not set -# CONFIG_HNS_DSAF is not set -# CONFIG_HNS_ENET is not set -CONFIG_NET_VENDOR_HUAWEI=y -CONFIG_NET_VENDOR_I825XX=y -CONFIG_NET_VENDOR_INTEL=y -CONFIG_NET_VENDOR_MARVELL=y -# CONFIG_MVMDIO is not set -CONFIG_NET_VENDOR_MICREL=y -# CONFIG_KS8851_MLL is not set -CONFIG_NET_VENDOR_MICROCHIP=y -CONFIG_NET_VENDOR_MICROSEMI=y -CONFIG_NET_VENDOR_NATSEMI=y -CONFIG_NET_VENDOR_NETRONOME=y -CONFIG_NET_VENDOR_NI=y -# CONFIG_NI_XGE_MANAGEMENT_ENET is not set -CONFIG_NET_VENDOR_8390=y -# CONFIG_AX88796 is not set -# CONFIG_ETHOC is not set -CONFIG_NET_VENDOR_QUALCOMM=y -# CONFIG_QCOM_EMAC is not set -# CONFIG_RMNET is not set -CONFIG_NET_VENDOR_RENESAS=y -CONFIG_NET_VENDOR_ROCKER=y -CONFIG_NET_VENDOR_SAMSUNG=y -# CONFIG_SXGBE_ETH is not set -CONFIG_NET_VENDOR_SEEQ=y -CONFIG_NET_VENDOR_SOLARFLARE=y -CONFIG_NET_VENDOR_SMSC=y -# CONFIG_SMC911X is not set -# CONFIG_SMSC911X is not set -CONFIG_NET_VENDOR_SOCIONEXT=y -CONFIG_NET_VENDOR_STMICRO=y -# CONFIG_STMMAC_ETH is not set -CONFIG_NET_VENDOR_SYNOPSYS=y -# CONFIG_DWC_XLGMAC is not set -CONFIG_NET_VENDOR_VIA=y -# CONFIG_VIA_RHINE is not set -# CONFIG_VIA_VELOCITY is not set -CONFIG_NET_VENDOR_WIZNET=y -# CONFIG_WIZNET_W5100 is not set -# CONFIG_WIZNET_W5300 is not set -# CONFIG_MDIO_DEVICE is not set -# CONFIG_PHYLIB is not set -# CONFIG_PPP is not set -# CONFIG_SLIP is not set - -# -# Host-side USB support is needed for USB Network Adapter support -# -CONFIG_WLAN=y -# CONFIG_WIRELESS_WDS is not set -CONFIG_WLAN_VENDOR_ADMTEK=y -CONFIG_WLAN_VENDOR_ATH=y -# CONFIG_ATH_DEBUG is not set -CONFIG_WLAN_VENDOR_ATMEL=y -CONFIG_WLAN_VENDOR_BROADCOM=y -CONFIG_WLAN_VENDOR_CISCO=y -CONFIG_WLAN_VENDOR_INTEL=y -CONFIG_WLAN_VENDOR_INTERSIL=y -# CONFIG_HOSTAP is not set -CONFIG_WLAN_VENDOR_MARVELL=y -CONFIG_WLAN_VENDOR_MEDIATEK=y -CONFIG_WLAN_VENDOR_RALINK=y -CONFIG_WLAN_VENDOR_REALTEK=y -CONFIG_WLAN_VENDOR_RSI=y -CONFIG_WLAN_VENDOR_ST=y -CONFIG_WLAN_VENDOR_TI=y -CONFIG_WLAN_VENDOR_ZYDAS=y -CONFIG_WLAN_VENDOR_QUANTENNA=y - -# -# Enable WiMAX (Networking options) to see the WiMAX drivers -# -# CONFIG_WAN is not set -CONFIG_NET_FAILOVER=y -# CONFIG_ISDN is not set -# CONFIG_NVM is not set - -# -# Input device support -# -CONFIG_INPUT=y -# CONFIG_INPUT_FF_MEMLESS is not set -# CONFIG_INPUT_POLLDEV is not set -# CONFIG_INPUT_SPARSEKMAP is not set -# CONFIG_INPUT_MATRIXKMAP is not set - -# -# Userland interfaces -# -# CONFIG_INPUT_MOUSEDEV is not set -# CONFIG_INPUT_JOYDEV is not set -# CONFIG_INPUT_EVDEV is not set -# CONFIG_INPUT_EVBUG is not set - -# -# Input Device Drivers -# -CONFIG_INPUT_KEYBOARD=y -CONFIG_KEYBOARD_ATKBD=y -# CONFIG_KEYBOARD_LKKBD is not set -# CONFIG_KEYBOARD_NEWTON is not set -# CONFIG_KEYBOARD_OPENCORES is not set -# CONFIG_KEYBOARD_SAMSUNG is not set -# CONFIG_KEYBOARD_STOWAWAY is not set -# CONFIG_KEYBOARD_SUNKBD is not set -# CONFIG_KEYBOARD_OMAP4 is not set -# CONFIG_KEYBOARD_XTKBD is not set -# CONFIG_KEYBOARD_BCM is not set -CONFIG_INPUT_MOUSE=y -CONFIG_MOUSE_PS2=y -CONFIG_MOUSE_PS2_ALPS=y -CONFIG_MOUSE_PS2_BYD=y -CONFIG_MOUSE_PS2_LOGIPS2PP=y -CONFIG_MOUSE_PS2_SYNAPTICS=y -CONFIG_MOUSE_PS2_CYPRESS=y -CONFIG_MOUSE_PS2_TRACKPOINT=y -# CONFIG_MOUSE_PS2_ELANTECH is not set -# CONFIG_MOUSE_PS2_SENTELIC is not set -# CONFIG_MOUSE_PS2_TOUCHKIT is not set -CONFIG_MOUSE_PS2_FOCALTECH=y -# CONFIG_MOUSE_SERIAL is not set -# CONFIG_MOUSE_VSXXXAA is not set -# CONFIG_INPUT_JOYSTICK is not set -# CONFIG_INPUT_TABLET is not set -# CONFIG_INPUT_TOUCHSCREEN is not set -# CONFIG_INPUT_MISC is not set -# CONFIG_RMI4_CORE is not set - -# -# Hardware I/O ports -# -CONFIG_SERIO=y -CONFIG_SERIO_SERPORT=y -# CONFIG_SERIO_AMBAKMI is not set -CONFIG_SERIO_LIBPS2=y -# CONFIG_SERIO_RAW is not set -# CONFIG_SERIO_ALTERA_PS2 is not set -# CONFIG_SERIO_PS2MULT is not set -# CONFIG_SERIO_ARC_PS2 is not set -# CONFIG_SERIO_APBPS2 is not set -# CONFIG_USERIO is not set -# CONFIG_GAMEPORT is not set -# end of Hardware I/O ports -# end of Input device support - -# -# Character devices -# -CONFIG_TTY=y -CONFIG_VT=y -CONFIG_CONSOLE_TRANSLATIONS=y -CONFIG_VT_CONSOLE=y -CONFIG_HW_CONSOLE=y -# CONFIG_VT_HW_CONSOLE_BINDING is not set -CONFIG_UNIX98_PTYS=y -CONFIG_LEGACY_PTYS=y -CONFIG_LEGACY_PTY_COUNT=256 -# CONFIG_SERIAL_NONSTANDARD is not set -# CONFIG_N_GSM is not set -# CONFIG_TRACE_SINK is not set -# CONFIG_NULL_TTY is not set -CONFIG_LDISC_AUTOLOAD=y -CONFIG_DEVMEM=y -# CONFIG_DEVKMEM is not set - -# -# Serial drivers -# -CONFIG_SERIAL_EARLYCON=y -# CONFIG_SERIAL_8250 is not set - -# -# Non-8250 serial port support -# -# CONFIG_SERIAL_AMBA_PL010 is not set -CONFIG_SERIAL_AMBA_PL011=y -CONFIG_SERIAL_AMBA_PL011_CONSOLE=y -# CONFIG_SERIAL_EARLYCON_ARM_SEMIHOST is not set -# CONFIG_SERIAL_UARTLITE is not set -CONFIG_SERIAL_CORE=y -CONFIG_SERIAL_CORE_CONSOLE=y -# CONFIG_SERIAL_SIFIVE is not set -# CONFIG_SERIAL_SCCNXP is not set -# CONFIG_SERIAL_BCM63XX is not set -# CONFIG_SERIAL_ALTERA_JTAGUART is not set -# CONFIG_SERIAL_ALTERA_UART is not set -# CONFIG_SERIAL_XILINX_PS_UART is not set -# CONFIG_SERIAL_ARC is not set -# CONFIG_SERIAL_FSL_LPUART is not set -# CONFIG_SERIAL_CONEXANT_DIGICOLOR is not set -# CONFIG_SERIAL_ST_ASC is not set -# end of Serial drivers - -# CONFIG_SERIAL_DEV_BUS is not set -# CONFIG_TTY_PRINTK is not set -# CONFIG_HVC_DCC is not set -# CONFIG_VIRTIO_CONSOLE is not set -# CONFIG_IPMI_HANDLER is not set -# CONFIG_HW_RANDOM is not set -# CONFIG_RAW_DRIVER is not set -# CONFIG_TCG_TPM is not set -# CONFIG_XILLYBUS is not set -# end of Character devices - -# -# I2C support -# -# CONFIG_I2C is not set -# end of I2C support - -# CONFIG_I3C is not set -# CONFIG_SPI is not set -# CONFIG_SPMI is not set -# CONFIG_HSI is not set -# CONFIG_PPS is not set - -# -# PTP clock support -# - -# -# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. -# -# end of PTP clock support - -# CONFIG_PINCTRL is not set -CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y -# CONFIG_GPIOLIB is not set -# CONFIG_W1 is not set -# CONFIG_POWER_AVS is not set -# CONFIG_POWER_RESET is not set -# CONFIG_POWER_SUPPLY is not set -# CONFIG_HWMON is not set -# CONFIG_THERMAL is not set -# CONFIG_WATCHDOG is not set -CONFIG_SSB_POSSIBLE=y -# CONFIG_SSB is not set -CONFIG_BCMA_POSSIBLE=y -# CONFIG_BCMA is not set - -# -# Multifunction device drivers -# -# CONFIG_MFD_ATMEL_FLEXCOM is not set -# CONFIG_MFD_ATMEL_HLCDC is not set -# CONFIG_MFD_CROS_EC is not set -# CONFIG_MFD_MADERA is not set -# CONFIG_MFD_HI6421_PMIC is not set -# CONFIG_HTC_PASIC3 is not set -# CONFIG_MFD_KEMPLD is not set -# CONFIG_MFD_MT6397 is not set -# CONFIG_MFD_PM8XXX is not set -# CONFIG_MFD_SM501 is not set -# CONFIG_ABX500_CORE is not set -# CONFIG_MFD_SYSCON is not set -# CONFIG_MFD_TI_AM335X_TSCADC is not set -# CONFIG_MFD_T7L66XB is not set -# CONFIG_MFD_TC6387XB is not set -# CONFIG_MFD_TC6393XB is not set -# CONFIG_MFD_TQMX86 is not set -# end of Multifunction device drivers - -# CONFIG_REGULATOR is not set -# CONFIG_RC_CORE is not set -# CONFIG_MEDIA_SUPPORT is not set - -# -# Graphics support -# -# CONFIG_IMX_IPUV3_CORE is not set -# CONFIG_DRM is not set -# CONFIG_DRM_DP_CEC is not set - -# -# ARM devices -# -# end of ARM devices - -# -# ACP (Audio CoProcessor) Configuration -# -# end of ACP (Audio CoProcessor) Configuration - -# -# Frame buffer Devices -# -# CONFIG_FB is not set -# end of Frame buffer Devices - -# -# Backlight & LCD device support -# -CONFIG_LCD_CLASS_DEVICE=y -# CONFIG_LCD_PLATFORM is not set -CONFIG_BACKLIGHT_CLASS_DEVICE=y -CONFIG_BACKLIGHT_GENERIC=y -# CONFIG_BACKLIGHT_PM8941_WLED is not set -# end of Backlight & LCD device support - -# -# Console display driver support -# -CONFIG_DUMMY_CONSOLE=y -# end of Console display driver support -# end of Graphics support - -# CONFIG_SOUND is not set - -# -# HID support -# -CONFIG_HID=y -# CONFIG_HID_BATTERY_STRENGTH is not set -# CONFIG_HIDRAW is not set -# CONFIG_UHID is not set -CONFIG_HID_GENERIC=y - -# -# Special HID drivers -# -# CONFIG_HID_A4TECH is not set -# CONFIG_HID_ACRUX is not set -# CONFIG_HID_APPLE is not set -# CONFIG_HID_AUREAL is not set -# CONFIG_HID_BELKIN is not set -# CONFIG_HID_CHERRY is not set -# CONFIG_HID_CHICONY is not set -# CONFIG_HID_COUGAR is not set -# CONFIG_HID_MACALLY is not set -# CONFIG_HID_CMEDIA is not set -# CONFIG_HID_CYPRESS is not set -# CONFIG_HID_DRAGONRISE is not set -# CONFIG_HID_EMS_FF is not set -# CONFIG_HID_ELECOM is not set -# CONFIG_HID_EZKEY is not set -# CONFIG_HID_GEMBIRD is not set -# CONFIG_HID_GFRM is not set -# CONFIG_HID_KEYTOUCH is not set -# CONFIG_HID_KYE is not set -# CONFIG_HID_WALTOP is not set -# CONFIG_HID_VIEWSONIC is not set -# CONFIG_HID_GYRATION is not set -# CONFIG_HID_ICADE is not set -# CONFIG_HID_ITE is not set -# CONFIG_HID_JABRA is not set -# CONFIG_HID_TWINHAN is not set -# CONFIG_HID_KENSINGTON is not set -# CONFIG_HID_LCPOWER is not set -# CONFIG_HID_LENOVO is not set -# CONFIG_HID_LOGITECH is not set -# CONFIG_HID_MAGICMOUSE is not set -# CONFIG_HID_MALTRON is not set -# CONFIG_HID_MAYFLASH is not set -# CONFIG_HID_REDRAGON is not set -# CONFIG_HID_MICROSOFT is not set -# CONFIG_HID_MONTEREY is not set -# CONFIG_HID_MULTITOUCH is not set -# CONFIG_HID_NTI is not set -# CONFIG_HID_ORTEK is not set -# CONFIG_HID_PANTHERLORD is not set -# CONFIG_HID_PETALYNX is not set -# CONFIG_HID_PICOLCD is not set -# CONFIG_HID_PLANTRONICS is not set -# CONFIG_HID_PRIMAX is not set -# CONFIG_HID_SAITEK is not set -# CONFIG_HID_SAMSUNG is not set -# CONFIG_HID_SPEEDLINK is not set -# CONFIG_HID_STEAM is not set -# CONFIG_HID_STEELSERIES is not set -# CONFIG_HID_SUNPLUS is not set -# CONFIG_HID_RMI is not set -# CONFIG_HID_GREENASIA is not set -# CONFIG_HID_SMARTJOYPLUS is not set -# CONFIG_HID_TIVO is not set -# CONFIG_HID_TOPSEED is not set -# CONFIG_HID_THRUSTMASTER is not set -# CONFIG_HID_UDRAW_PS3 is not set -# CONFIG_HID_XINMO is not set -# CONFIG_HID_ZEROPLUS is not set -# CONFIG_HID_ZYDACRON is not set -# CONFIG_HID_SENSOR_HUB is not set -# CONFIG_HID_ALPS is not set -# end of Special HID drivers -# end of HID support - -CONFIG_USB_OHCI_LITTLE_ENDIAN=y -# CONFIG_USB_SUPPORT is not set -# CONFIG_UWB is not set -# CONFIG_MMC is not set -# CONFIG_MEMSTICK is not set -# CONFIG_NEW_LEDS is not set -# CONFIG_ACCESSIBILITY is not set -# CONFIG_INFINIBAND is not set -CONFIG_EDAC_ATOMIC_SCRUB=y -CONFIG_EDAC_SUPPORT=y -CONFIG_RTC_LIB=y -# CONFIG_RTC_CLASS is not set -# CONFIG_DMADEVICES is not set - -# -# DMABUF options -# -# CONFIG_SYNC_FILE is not set -# end of DMABUF options - -# CONFIG_AUXDISPLAY is not set -# CONFIG_UIO is not set -# CONFIG_VIRT_DRIVERS is not set -CONFIG_VIRTIO=y -CONFIG_VIRTIO_MENU=y -# CONFIG_VIRTIO_BALLOON is not set -CONFIG_VIRTIO_INPUT=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y - -# -# Microsoft Hyper-V guest support -# -# end of Microsoft Hyper-V guest support - -# CONFIG_STAGING is not set -# CONFIG_GOLDFISH is not set -# CONFIG_CHROME_PLATFORMS is not set -# CONFIG_MELLANOX_PLATFORM is not set -CONFIG_CLKDEV_LOOKUP=y -CONFIG_HAVE_CLK_PREPARE=y -CONFIG_COMMON_CLK=y - -# -# Common Clock Framework -# -# CONFIG_CLK_HSDK is not set -# CONFIG_CLK_QORIQ is not set -# CONFIG_COMMON_CLK_FIXED_MMIO is not set -# end of Common Clock Framework - -# CONFIG_HWSPINLOCK is not set - -# -# Clock Source drivers -# -CONFIG_TIMER_OF=y -CONFIG_TIMER_PROBE=y -CONFIG_ARM_ARCH_TIMER=y -CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y -# end of Clock Source drivers - -# CONFIG_MAILBOX is not set -CONFIG_IOMMU_SUPPORT=y - -# -# Generic IOMMU Pagetable Support -# -# CONFIG_IOMMU_IO_PGTABLE_LPAE is not set -# CONFIG_IOMMU_IO_PGTABLE_ARMV7S is not set -# end of Generic IOMMU Pagetable Support - -# CONFIG_ARM_SMMU is not set - -# -# Remoteproc drivers -# -# CONFIG_REMOTEPROC is not set -# end of Remoteproc drivers - -# -# Rpmsg drivers -# -# CONFIG_RPMSG_VIRTIO is not set -# end of Rpmsg drivers - -# CONFIG_SOUNDWIRE is not set - -# -# SOC (System On Chip) specific Drivers -# - -# -# Amlogic SoC drivers -# -# end of Amlogic SoC drivers - -# -# Aspeed SoC drivers -# -# end of Aspeed SoC drivers - -# -# Broadcom SoC drivers -# -# CONFIG_SOC_BRCMSTB is not set -# end of Broadcom SoC drivers - -# -# NXP/Freescale QorIQ SoC drivers -# -# end of NXP/Freescale QorIQ SoC drivers - -# -# i.MX SoC drivers -# -# end of i.MX SoC drivers - -# -# IXP4xx SoC drivers -# -# CONFIG_IXP4XX_QMGR is not set -# CONFIG_IXP4XX_NPE is not set -# end of IXP4xx SoC drivers - -# -# Qualcomm SoC drivers -# -# end of Qualcomm SoC drivers - -# CONFIG_SOC_TI is not set - -# -# Xilinx SoC drivers -# -# CONFIG_XILINX_VCU is not set -# end of Xilinx SoC drivers -# end of SOC (System On Chip) specific Drivers - -# CONFIG_PM_DEVFREQ is not set -# CONFIG_EXTCON is not set -# CONFIG_MEMORY is not set -# CONFIG_IIO is not set -# CONFIG_PWM is not set - -# -# IRQ chip support -# -CONFIG_IRQCHIP=y -CONFIG_ARM_GIC=y -CONFIG_ARM_GIC_MAX_NR=1 -CONFIG_ARM_GIC_V3=y -CONFIG_ARM_GIC_V3_ITS=y -# CONFIG_AL_FIC is not set -CONFIG_PARTITION_PERCPU=y -# end of IRQ chip support - -# CONFIG_IPACK_BUS is not set -# CONFIG_RESET_CONTROLLER is not set - -# -# PHY Subsystem -# -# CONFIG_GENERIC_PHY is not set -# CONFIG_BCM_KONA_USB2_PHY is not set -# CONFIG_PHY_CADENCE_DP is not set -# CONFIG_PHY_CADENCE_DPHY is not set -# CONFIG_PHY_FSL_IMX8MQ_USB is not set -# CONFIG_PHY_PXA_28NM_HSIC is not set -# CONFIG_PHY_PXA_28NM_USB2 is not set -# end of PHY Subsystem - -# CONFIG_POWERCAP is not set -# CONFIG_MCB is not set -# CONFIG_RAS is not set - -# -# Android -# -# CONFIG_ANDROID is not set -# end of Android - -# CONFIG_DAX is not set -# CONFIG_NVMEM is not set - -# -# HW tracing support -# -# CONFIG_STM is not set -# CONFIG_INTEL_TH is not set -# end of HW tracing support - -# CONFIG_FPGA is not set -# CONFIG_FSI is not set -# CONFIG_TEE is not set -# CONFIG_SIOX is not set -# CONFIG_SLIMBUS is not set -# CONFIG_INTERCONNECT is not set -# CONFIG_COUNTER is not set -# end of Device Drivers - -# -# File systems -# -CONFIG_DCACHE_WORD_ACCESS=y -# CONFIG_VALIDATE_FS_PARSER is not set -# CONFIG_EXT2_FS is not set -# CONFIG_EXT3_FS is not set -# CONFIG_EXT4_FS is not set -# CONFIG_REISERFS_FS is not set -# CONFIG_JFS_FS is not set -# CONFIG_XFS_FS is not set -# CONFIG_GFS2_FS is not set -# CONFIG_BTRFS_FS is not set -# CONFIG_NILFS2_FS is not set -# CONFIG_F2FS_FS is not set -# CONFIG_EXPORTFS_BLOCK_OPS is not set -# CONFIG_FILE_LOCKING is not set -# CONFIG_FS_ENCRYPTION is not set -# CONFIG_DNOTIFY is not set -# CONFIG_INOTIFY_USER is not set -# CONFIG_FANOTIFY is not set -# CONFIG_QUOTA is not set -# CONFIG_AUTOFS4_FS is not set -# CONFIG_AUTOFS_FS is not set -# CONFIG_FUSE_FS is not set -# CONFIG_OVERLAY_FS is not set - -# -# Caches -# -# CONFIG_FSCACHE is not set -# end of Caches - -# -# CD-ROM/DVD Filesystems -# -# CONFIG_ISO9660_FS is not set -# CONFIG_UDF_FS is not set -# end of CD-ROM/DVD Filesystems - -# -# DOS/FAT/NT Filesystems -# -CONFIG_FAT_FS=y -CONFIG_MSDOS_FS=y -CONFIG_VFAT_FS=y -CONFIG_FAT_DEFAULT_CODEPAGE=437 -CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" -# CONFIG_FAT_DEFAULT_UTF8 is not set -# CONFIG_NTFS_FS is not set -# end of DOS/FAT/NT Filesystems - -# -# Pseudo filesystems -# -CONFIG_PROC_FS=y -CONFIG_PROC_SYSCTL=y -CONFIG_PROC_PAGE_MONITOR=y -# CONFIG_PROC_CHILDREN is not set -# CONFIG_SYSFS is not set -CONFIG_TMPFS=y -# CONFIG_CONFIGFS_FS is not set -# end of Pseudo filesystems - -CONFIG_MISC_FILESYSTEMS=y -# CONFIG_ORANGEFS_FS is not set -# CONFIG_ADFS_FS is not set -# CONFIG_AFFS_FS is not set -# CONFIG_HFS_FS is not set -# CONFIG_HFSPLUS_FS is not set -# CONFIG_BEFS_FS is not set -# CONFIG_BFS_FS is not set -# CONFIG_EFS_FS is not set -# CONFIG_CRAMFS is not set -CONFIG_SQUASHFS=y -CONFIG_SQUASHFS_FILE_CACHE=y -# CONFIG_SQUASHFS_FILE_DIRECT is not set -CONFIG_SQUASHFS_DECOMP_SINGLE=y -# CONFIG_SQUASHFS_DECOMP_MULTI is not set -# CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU is not set -# CONFIG_SQUASHFS_XATTR is not set -CONFIG_SQUASHFS_ZLIB=y -# CONFIG_SQUASHFS_LZ4 is not set -# CONFIG_SQUASHFS_LZO is not set -# CONFIG_SQUASHFS_XZ is not set -# CONFIG_SQUASHFS_ZSTD is not set -# CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set -# CONFIG_SQUASHFS_EMBEDDED is not set -CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 -# CONFIG_VXFS_FS is not set -# CONFIG_MINIX_FS is not set -# CONFIG_OMFS_FS is not set -# CONFIG_HPFS_FS is not set -# CONFIG_QNX4FS_FS is not set -# CONFIG_QNX6FS_FS is not set -# CONFIG_ROMFS_FS is not set -# CONFIG_PSTORE is not set -# CONFIG_SYSV_FS is not set -# CONFIG_UFS_FS is not set -CONFIG_NETWORK_FILESYSTEMS=y -# CONFIG_CEPH_FS is not set -# CONFIG_CIFS is not set -# CONFIG_CODA_FS is not set -# CONFIG_AFS_FS is not set -CONFIG_9P_FS=y -# CONFIG_9P_FS_POSIX_ACL is not set -# CONFIG_9P_FS_SECURITY is not set -CONFIG_NLS=y -CONFIG_NLS_DEFAULT="iso8859-1" -CONFIG_NLS_CODEPAGE_437=y -# CONFIG_NLS_CODEPAGE_737 is not set -# CONFIG_NLS_CODEPAGE_775 is not set -# CONFIG_NLS_CODEPAGE_850 is not set -# CONFIG_NLS_CODEPAGE_852 is not set -# CONFIG_NLS_CODEPAGE_855 is not set -# CONFIG_NLS_CODEPAGE_857 is not set -# CONFIG_NLS_CODEPAGE_860 is not set -# CONFIG_NLS_CODEPAGE_861 is not set -# CONFIG_NLS_CODEPAGE_862 is not set -# CONFIG_NLS_CODEPAGE_863 is not set -# CONFIG_NLS_CODEPAGE_864 is not set -# CONFIG_NLS_CODEPAGE_865 is not set -# CONFIG_NLS_CODEPAGE_866 is not set -# CONFIG_NLS_CODEPAGE_869 is not set -# CONFIG_NLS_CODEPAGE_936 is not set -# CONFIG_NLS_CODEPAGE_950 is not set -# CONFIG_NLS_CODEPAGE_932 is not set -# CONFIG_NLS_CODEPAGE_949 is not set -# CONFIG_NLS_CODEPAGE_874 is not set -# CONFIG_NLS_ISO8859_8 is not set -# CONFIG_NLS_CODEPAGE_1250 is not set -# CONFIG_NLS_CODEPAGE_1251 is not set -# CONFIG_NLS_ASCII is not set -CONFIG_NLS_ISO8859_1=y -# CONFIG_NLS_ISO8859_2 is not set -# CONFIG_NLS_ISO8859_3 is not set -# CONFIG_NLS_ISO8859_4 is not set -# CONFIG_NLS_ISO8859_5 is not set -# CONFIG_NLS_ISO8859_6 is not set -# CONFIG_NLS_ISO8859_7 is not set -# CONFIG_NLS_ISO8859_9 is not set -# CONFIG_NLS_ISO8859_13 is not set -# CONFIG_NLS_ISO8859_14 is not set -# CONFIG_NLS_ISO8859_15 is not set -# CONFIG_NLS_KOI8_R is not set -# CONFIG_NLS_KOI8_U is not set -# CONFIG_NLS_MAC_ROMAN is not set -# CONFIG_NLS_MAC_CELTIC is not set -# CONFIG_NLS_MAC_CENTEURO is not set -# CONFIG_NLS_MAC_CROATIAN is not set -# CONFIG_NLS_MAC_CYRILLIC is not set -# CONFIG_NLS_MAC_GAELIC is not set -# CONFIG_NLS_MAC_GREEK is not set -# CONFIG_NLS_MAC_ICELAND is not set -# CONFIG_NLS_MAC_INUIT is not set -# CONFIG_NLS_MAC_ROMANIAN is not set -# CONFIG_NLS_MAC_TURKISH is not set -# CONFIG_NLS_UTF8 is not set -# CONFIG_UNICODE is not set -# end of File systems - -# -# Security options -# -# CONFIG_KEYS is not set -# CONFIG_SECURITY_DMESG_RESTRICT is not set -# CONFIG_SECURITYFS is not set -# CONFIG_FORTIFY_SOURCE is not set -# CONFIG_STATIC_USERMODEHELPER is not set -CONFIG_DEFAULT_SECURITY_DAC=y -CONFIG_LSM="yama,loadpin,safesetid,integrity" - -# -# Kernel hardening options -# - -# -# Memory initialization -# -CONFIG_INIT_STACK_NONE=y -# end of Memory initialization -# end of Kernel hardening options -# end of Security options - -CONFIG_CRYPTO=y - -# -# Crypto core or helper -# -CONFIG_CRYPTO_ALGAPI=y -CONFIG_CRYPTO_ALGAPI2=y -CONFIG_CRYPTO_AEAD=y -CONFIG_CRYPTO_AEAD2=y -CONFIG_CRYPTO_BLKCIPHER=y -CONFIG_CRYPTO_BLKCIPHER2=y -CONFIG_CRYPTO_HASH2=y -CONFIG_CRYPTO_RNG2=y -# CONFIG_CRYPTO_MANAGER is not set -# CONFIG_CRYPTO_USER is not set -# CONFIG_CRYPTO_NULL is not set -CONFIG_CRYPTO_NULL2=y -# CONFIG_CRYPTO_CRYPTD is not set -# CONFIG_CRYPTO_AUTHENC is not set -CONFIG_CRYPTO_ENGINE=y - -# -# Public-key cryptography -# -# CONFIG_CRYPTO_RSA is not set -# CONFIG_CRYPTO_DH is not set -# CONFIG_CRYPTO_ECDH is not set -# CONFIG_CRYPTO_ECRDSA is not set - -# -# Authenticated Encryption with Associated Data -# -# CONFIG_CRYPTO_CCM is not set -# CONFIG_CRYPTO_GCM is not set -# CONFIG_CRYPTO_CHACHA20POLY1305 is not set -# CONFIG_CRYPTO_AEGIS128 is not set -# CONFIG_CRYPTO_AEGIS128L is not set -# CONFIG_CRYPTO_AEGIS256 is not set -# CONFIG_CRYPTO_MORUS640 is not set -# CONFIG_CRYPTO_MORUS1280 is not set -# CONFIG_CRYPTO_SEQIV is not set -# CONFIG_CRYPTO_ECHAINIV is not set - -# -# Block modes -# -# CONFIG_CRYPTO_CBC is not set -# CONFIG_CRYPTO_CFB is not set -# CONFIG_CRYPTO_CTR is not set -# CONFIG_CRYPTO_CTS is not set -# CONFIG_CRYPTO_ECB is not set -# CONFIG_CRYPTO_LRW is not set -# CONFIG_CRYPTO_OFB is not set -# CONFIG_CRYPTO_PCBC is not set -# CONFIG_CRYPTO_XTS is not set -# CONFIG_CRYPTO_KEYWRAP is not set -# CONFIG_CRYPTO_ADIANTUM is not set - -# -# Hash modes -# -# CONFIG_CRYPTO_CMAC is not set -# CONFIG_CRYPTO_HMAC is not set -# CONFIG_CRYPTO_XCBC is not set -# CONFIG_CRYPTO_VMAC is not set - -# -# Digest -# -# CONFIG_CRYPTO_CRC32C is not set -# CONFIG_CRYPTO_CRC32 is not set -# CONFIG_CRYPTO_XXHASH is not set -# CONFIG_CRYPTO_CRCT10DIF is not set -# CONFIG_CRYPTO_GHASH is not set -# CONFIG_CRYPTO_POLY1305 is not set -# CONFIG_CRYPTO_MD4 is not set -# CONFIG_CRYPTO_MD5 is not set -# CONFIG_CRYPTO_MICHAEL_MIC is not set -# CONFIG_CRYPTO_RMD128 is not set -# CONFIG_CRYPTO_RMD160 is not set -# CONFIG_CRYPTO_RMD256 is not set -# CONFIG_CRYPTO_RMD320 is not set -# CONFIG_CRYPTO_SHA1 is not set -# CONFIG_CRYPTO_SHA256 is not set -# CONFIG_CRYPTO_SHA512 is not set -# CONFIG_CRYPTO_SHA3 is not set -# CONFIG_CRYPTO_SM3 is not set -# CONFIG_CRYPTO_STREEBOG is not set -# CONFIG_CRYPTO_TGR192 is not set -# CONFIG_CRYPTO_WP512 is not set - -# -# Ciphers -# -CONFIG_CRYPTO_AES=y -# CONFIG_CRYPTO_AES_TI is not set -# CONFIG_CRYPTO_ANUBIS is not set -# CONFIG_CRYPTO_ARC4 is not set -# CONFIG_CRYPTO_BLOWFISH is not set -# CONFIG_CRYPTO_CAMELLIA is not set -# CONFIG_CRYPTO_CAST5 is not set -# CONFIG_CRYPTO_CAST6 is not set -# CONFIG_CRYPTO_DES is not set -# CONFIG_CRYPTO_FCRYPT is not set -# CONFIG_CRYPTO_KHAZAD is not set -# CONFIG_CRYPTO_SALSA20 is not set -# CONFIG_CRYPTO_CHACHA20 is not set -# CONFIG_CRYPTO_SEED is not set -# CONFIG_CRYPTO_SERPENT is not set -# CONFIG_CRYPTO_SM4 is not set -# CONFIG_CRYPTO_TEA is not set -# CONFIG_CRYPTO_TWOFISH is not set - -# -# Compression -# -# CONFIG_CRYPTO_DEFLATE is not set -# CONFIG_CRYPTO_LZO is not set -# CONFIG_CRYPTO_842 is not set -# CONFIG_CRYPTO_LZ4 is not set -# CONFIG_CRYPTO_LZ4HC is not set -# CONFIG_CRYPTO_ZSTD is not set - -# -# Random Number Generation -# -# CONFIG_CRYPTO_ANSI_CPRNG is not set -# CONFIG_CRYPTO_DRBG_MENU is not set -# CONFIG_CRYPTO_JITTERENTROPY is not set -# CONFIG_CRYPTO_USER_API_HASH is not set -# CONFIG_CRYPTO_USER_API_SKCIPHER is not set -# CONFIG_CRYPTO_USER_API_RNG is not set -# CONFIG_CRYPTO_USER_API_AEAD is not set -CONFIG_CRYPTO_HW=y -CONFIG_CRYPTO_DEV_VIRTIO=y -# CONFIG_CRYPTO_DEV_CCREE is not set - -# -# Certificates for signature checking -# -# end of Certificates for signature checking - -# -# Library routines -# -# CONFIG_PACKING is not set -CONFIG_BITREVERSE=y -CONFIG_HAVE_ARCH_BITREVERSE=y -CONFIG_GENERIC_STRNCPY_FROM_USER=y -CONFIG_GENERIC_STRNLEN_USER=y -CONFIG_GENERIC_NET_UTILS=y -# CONFIG_CORDIC is not set -CONFIG_RATIONAL=y -CONFIG_GENERIC_PCI_IOMAP=y -CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y -# CONFIG_CRC_CCITT is not set -# CONFIG_CRC16 is not set -# CONFIG_CRC_T10DIF is not set -# CONFIG_CRC_ITU_T is not set -CONFIG_CRC32=y -# CONFIG_CRC32_SELFTEST is not set -CONFIG_CRC32_SLICEBY8=y -# CONFIG_CRC32_SLICEBY4 is not set -# CONFIG_CRC32_SARWATE is not set -# CONFIG_CRC32_BIT is not set -# CONFIG_CRC64 is not set -# CONFIG_CRC4 is not set -# CONFIG_CRC7 is not set -# CONFIG_LIBCRC32C is not set -# CONFIG_CRC8 is not set -# CONFIG_RANDOM32_SELFTEST is not set -CONFIG_ZLIB_INFLATE=y -CONFIG_LZO_DECOMPRESS=y -CONFIG_LZ4_DECOMPRESS=y -CONFIG_XZ_DEC=y -CONFIG_XZ_DEC_X86=y -CONFIG_XZ_DEC_POWERPC=y -CONFIG_XZ_DEC_IA64=y -CONFIG_XZ_DEC_ARM=y -CONFIG_XZ_DEC_ARMTHUMB=y -CONFIG_XZ_DEC_SPARC=y -CONFIG_XZ_DEC_BCJ=y -# CONFIG_XZ_DEC_TEST is not set -CONFIG_DECOMPRESS_GZIP=y -CONFIG_DECOMPRESS_BZIP2=y -CONFIG_DECOMPRESS_LZMA=y -CONFIG_DECOMPRESS_XZ=y -CONFIG_DECOMPRESS_LZO=y -CONFIG_DECOMPRESS_LZ4=y -CONFIG_GENERIC_ALLOCATOR=y -CONFIG_HAS_IOMEM=y -CONFIG_HAS_IOPORT_MAP=y -CONFIG_HAS_DMA=y -CONFIG_NEED_DMA_MAP_STATE=y -CONFIG_DMA_DECLARE_COHERENT=y -CONFIG_ARCH_HAS_SETUP_DMA_OPS=y -CONFIG_ARCH_HAS_TEARDOWN_DMA_OPS=y -CONFIG_DMA_REMAP=y -# CONFIG_DMA_API_DEBUG is not set -CONFIG_GLOB=y -# CONFIG_GLOB_SELFTEST is not set -CONFIG_NLATTR=y -# CONFIG_DDR is not set -# CONFIG_IRQ_POLL is not set -CONFIG_LIBFDT=y -CONFIG_SG_POOL=y -CONFIG_SBITMAP=y -# CONFIG_STRING_SELFTEST is not set -# end of Library routines - -# -# Kernel hacking -# - -# -# printk and dmesg options -# -# CONFIG_PRINTK_TIME is not set -# CONFIG_PRINTK_CALLER is not set -CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7 -CONFIG_CONSOLE_LOGLEVEL_QUIET=4 -CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 -# CONFIG_BOOT_PRINTK_DELAY is not set -# end of printk and dmesg options - -# -# Compile-time checks and compiler options -# -# CONFIG_DEBUG_INFO is not set -# CONFIG_ENABLE_MUST_CHECK is not set -CONFIG_FRAME_WARN=1024 -# CONFIG_STRIP_ASM_SYMS is not set -# CONFIG_READABLE_ASM is not set -# CONFIG_UNUSED_SYMBOLS is not set -# CONFIG_DEBUG_FS is not set -# CONFIG_HEADERS_CHECK is not set -# CONFIG_OPTIMIZE_INLINING is not set -# CONFIG_DEBUG_SECTION_MISMATCH is not set -# CONFIG_SECTION_MISMATCH_WARN_ONLY is not set -# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set -# end of Compile-time checks and compiler options - -# CONFIG_MAGIC_SYSRQ is not set -CONFIG_DEBUG_KERNEL=y -CONFIG_DEBUG_MISC=y - -# -# Memory Debugging -# -# CONFIG_PAGE_EXTENSION is not set -# CONFIG_DEBUG_PAGEALLOC is not set -# CONFIG_PAGE_OWNER is not set -# CONFIG_PAGE_POISONING is not set -# CONFIG_DEBUG_RODATA_TEST is not set -# CONFIG_DEBUG_OBJECTS is not set -CONFIG_HAVE_DEBUG_KMEMLEAK=y -# CONFIG_DEBUG_KMEMLEAK is not set -# CONFIG_DEBUG_STACK_USAGE is not set -# CONFIG_DEBUG_VM is not set -CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y -# CONFIG_DEBUG_VIRTUAL is not set -# CONFIG_DEBUG_MEMORY_INIT is not set -CONFIG_CC_HAS_KASAN_GENERIC=y -CONFIG_KASAN_STACK=1 -# end of Memory Debugging - -CONFIG_ARCH_HAS_KCOV=y -CONFIG_CC_HAS_SANCOV_TRACE_PC=y -# CONFIG_KCOV is not set -# CONFIG_DEBUG_SHIRQ is not set - -# -# Debug Lockups and Hangs -# -# CONFIG_SOFTLOCKUP_DETECTOR is not set -# CONFIG_DETECT_HUNG_TASK is not set -# CONFIG_WQ_WATCHDOG is not set -# end of Debug Lockups and Hangs - -# CONFIG_PANIC_ON_OOPS is not set -CONFIG_PANIC_ON_OOPS_VALUE=0 -CONFIG_PANIC_TIMEOUT=0 -CONFIG_SCHED_DEBUG=y -# CONFIG_SCHEDSTATS is not set -# CONFIG_SCHED_STACK_END_CHECK is not set -# CONFIG_DEBUG_TIMEKEEPING is not set - -# -# Lock Debugging (spinlocks, mutexes, etc...) -# -CONFIG_LOCK_DEBUGGING_SUPPORT=y -# CONFIG_PROVE_LOCKING is not set -# CONFIG_LOCK_STAT is not set -# CONFIG_DEBUG_RT_MUTEXES is not set -# CONFIG_DEBUG_SPINLOCK is not set -# CONFIG_DEBUG_MUTEXES is not set -# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set -# CONFIG_DEBUG_RWSEMS is not set -# CONFIG_DEBUG_LOCK_ALLOC is not set -# CONFIG_DEBUG_ATOMIC_SLEEP is not set -# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set -# CONFIG_LOCK_TORTURE_TEST is not set -# CONFIG_WW_MUTEX_SELFTEST is not set -# end of Lock Debugging (spinlocks, mutexes, etc...) - -# CONFIG_STACKTRACE is not set -# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set -# CONFIG_DEBUG_KOBJECT is not set -# CONFIG_DEBUG_LIST is not set -# CONFIG_DEBUG_PLIST is not set -# CONFIG_DEBUG_SG is not set -# CONFIG_DEBUG_NOTIFIERS is not set -# CONFIG_DEBUG_CREDENTIALS is not set - -# -# RCU Debugging -# -# CONFIG_RCU_PERF_TEST is not set -# CONFIG_RCU_TORTURE_TEST is not set -# CONFIG_RCU_TRACE is not set -# CONFIG_RCU_EQS_DEBUG is not set -# end of RCU Debugging - -# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set -# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set -# CONFIG_NOTIFIER_ERROR_INJECTION is not set -# CONFIG_FAULT_INJECTION is not set -# CONFIG_LATENCYTOP is not set -CONFIG_HAVE_FUNCTION_TRACER=y -CONFIG_HAVE_DYNAMIC_FTRACE=y -CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y -CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y -CONFIG_HAVE_SYSCALL_TRACEPOINTS=y -CONFIG_HAVE_C_RECORDMCOUNT=y -CONFIG_TRACING_SUPPORT=y -CONFIG_FTRACE=y -# CONFIG_FUNCTION_TRACER is not set -# CONFIG_PREEMPTIRQ_EVENTS is not set -# CONFIG_IRQSOFF_TRACER is not set -# CONFIG_SCHED_TRACER is not set -# CONFIG_HWLAT_TRACER is not set -# CONFIG_ENABLE_DEFAULT_TRACERS is not set -# CONFIG_FTRACE_SYSCALLS is not set -# CONFIG_TRACER_SNAPSHOT is not set -CONFIG_BRANCH_PROFILE_NONE=y -# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set -# CONFIG_PROFILE_ALL_BRANCHES is not set -# CONFIG_STACK_TRACER is not set -# CONFIG_TRACEPOINT_BENCHMARK is not set -# CONFIG_RUNTIME_TESTING_MENU is not set -# CONFIG_MEMTEST is not set -# CONFIG_BUG_ON_DATA_CORRUPTION is not set -# CONFIG_SAMPLES is not set -CONFIG_HAVE_ARCH_KGDB=y -# CONFIG_KGDB is not set -# CONFIG_UBSAN is not set -CONFIG_UBSAN_ALIGNMENT=y -CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y -# CONFIG_STRICT_DEVMEM is not set -# CONFIG_ARM_PTDUMP_DEBUGFS is not set -# CONFIG_DEBUG_WX is not set -CONFIG_UNWINDER_ARM=y -CONFIG_ARM_UNWIND=y -# CONFIG_DEBUG_USER is not set -# CONFIG_DEBUG_LL is not set -CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" -CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h" -# CONFIG_PID_IN_CONTEXTIDR is not set -# CONFIG_CORESIGHT is not set -# end of Kernel hacking diff --git a/.circleci/images/test-image-arm64/Dockerfile b/.circleci/images/test-image-arm64/Dockerfile deleted file mode 100644 index 4bc53287f5..0000000000 --- a/.circleci/images/test-image-arm64/Dockerfile +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2020-2021 the u-root Authors. All rights reserved -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -FROM cimg/go:1.21 - -# Install dependencies -RUN sudo apt-get update && \ - sudo apt-get install -y --no-install-recommends \ - `# Linux dependencies` \ - bc \ - bison \ - flex \ - gcc-aarch64-linux-gnu \ - git \ - libssl-dev \ - make \ - `# QEMU dependencies` \ - libattr1-dev \ - libcap-dev \ - libcap-ng-dev \ - libfdt-dev \ - libglib2.0-dev \ - libpixman-1-dev \ - meson \ - ninja-build \ - python3 \ - qemu-efi-aarch64 \ - zlib1g-dev \ - `# Linux kernel build deps` \ - libelf-dev && \ - sudo rm -rf /var/lib/apt/lists/* - -# Create working directory -WORKDIR /home/circleci -COPY config_linux.txt .config - -# Build linux -RUN set -eux; \ - git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; \ - sudo chmod 0444 .config; \ - mv .config linux/; \ - cd linux; \ - export ARCH=arm64; \ - export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-; \ - make olddefconfig; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp linux/arch/arm64/boot/Image Image; \ - rm -rf linux/ - -# Build QEMU -RUN set -eux; \ - git clone --depth=1 --branch=v7.0.0 https://github.com/qemu/qemu; \ - cd qemu; \ - mkdir build; \ - cd build; \ - ../configure \ - --target-list=aarch64-softmmu \ - --enable-virtfs \ - --disable-docs \ - --disable-sdl \ - --disable-kvm; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp -rL qemu/build/pc-bios/ ~/pc-bios; \ - cp qemu/build/aarch64-softmmu/qemu-system-aarch64 .; \ - rm -rf qemu/ - -# Export paths to binaries. -ENV UROOT_KERNEL /home/circleci/Image -ENV UROOT_QEMU "/home/circleci/qemu-system-aarch64 -machine virt -cpu max -m 1G " -ENV UROOT_TESTARCH arm64 diff --git a/.circleci/images/uefipayload-amd64/Dockerfile b/.circleci/images/uefipayload-amd64/Dockerfile new file mode 100644 index 0000000000..d6caed8321 --- /dev/null +++ b/.circleci/images/uefipayload-amd64/Dockerfile @@ -0,0 +1,42 @@ +# Copyright 2018-2021 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + git; + +RUN git clone --branch uefipayload-2024 --recursive https://github.com/linuxboot/edk2 uefipayload; + +RUN apt-get install -y --no-install-recommends \ + make \ + python3 \ + python3-dev \ + python3-pip \ + gcc \ + g++ \ + uuid-dev \ + nasm \ + bash \ + libfdt-dev \ + swig \ + iasl; + +RUN pip3 install --break-system-packages pefile pylibfdt + +SHELL ["/bin/bash", "-c"] +RUN cd uefipayload; \ + source ./edksetup.sh; \ + make -C BaseTools; \ + build -a X64 -p UefiPayloadPkg/UefiPayloadPkg.dsc -b DEBUG \ + -t GCC5 -D BOOTLOADER=LINUXBOOT -D DISABLE_MMX_SSE=true; \ + cp /uefipayload/Build/UefiPayloadPkgX64/DEBUG_GCC5/FV/UEFIPAYLOAD.fd /UEFIPAYLOAD.fd; \ + python3 UefiPayloadPkg/UniversalPayloadBuild.py -t GCC5 --Fit; + +FROM scratch +COPY --from=base /UEFIPAYLOAD.fd /UEFIPAYLOAD.fd +COPY --from=base /uefipayload/Build/UefiPayloadPkgX64/UniversalPayload.fit /UEFIPayload.fit diff --git a/.github/workflows/test-images.yml b/.github/workflows/test-images.yml new file mode 100644 index 0000000000..f5bfcf95ba --- /dev/null +++ b/.github/workflows/test-images.yml @@ -0,0 +1,69 @@ +name: Publish test images + +on: + push: + paths: + - '.circleci/images/kernel-arm/*' + - '.circleci/images/kernel-arm64/*' + - '.circleci/images/kernel-amd64/*' + - '.circleci/images/multiboot-test-kernel-amd64/*' + - '.circleci/images/uefipayload-amd64/*' + - '.github/workflows/test-images.yml' + branches: ['main'] + pull_request: + paths: + - '.circleci/images/kernel-arm/*' + - '.circleci/images/kernel-arm64/*' + - '.circleci/images/kernel-amd64/*' + - '.circleci/images/multiboot-test-kernel-amd64/*' + - '.circleci/images/uefipayload-amd64/*' + - '.github/workflows/test-images.yml' + branches: ['main'] + +# Cancel running workflows on new push to a PR. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + +jobs: + build-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + strategy: + matrix: + image: ['kernel-amd64', 'kernel-arm', 'kernel-arm64', 'uefipayload-amd64', 'multiboot-test-kernel-amd64'] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/test-${{ matrix.image }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: ./.circleci/images/${{ matrix.image }} + # Build for PRs, only push for main. + push: ${{ github.ref == 'refs/heads/main' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c911cf022b..7770f59623 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,30 +17,33 @@ jobs: strategy: matrix: go-version: [ '1.21.x' ] - vmarch: [ 'amd64', 'arm64' ] + vmarch: [ 'amd64', 'arm', 'arm64' ] pattern: [ 'integration/generic-tests/...', 'integration/gotests/...' ] - extra-arg: [ '' ] include: # Only run these with VM arch amd64 for now. - go-version: '1.21.x' vmarch: 'amd64' pattern: 'cmds/...' + - go-version: '1.21.x' vmarch: 'amd64' pattern: 'pkg/...' extra-arg: '-coverpkg=./pkg/...' - # This doesn't pass on 1.21.x for some reason yet. - - go-version: '1.20' - vmarch: arm - pattern: 'integration/generic-tests/...' + # Root dir tests - go-version: '1.21.x' vmarch: 'amd64' pattern: '.' + # QEMU's -M virt only supports GOARM=5, so add goarm=5 only for + # arm configs + - vmarch: arm + goarm: '5' + env: GO_VERSION: ${{ matrix.go-version }} VMTEST_ARCH: ${{ matrix.vmarch }} + GOARM: ${{ matrix.goarm }} steps: - uses: actions/checkout@v4 @@ -55,11 +58,14 @@ jobs: - name: Test run: | mkdir gocov - GOCOVERDIR=$(pwd)/gocov \ + VMTEST_GOCOVERDIR=$(pwd)/gocov \ VMTEST_GO_PROFILE=vmcoverage.txt runvmtest -- \ go test -v -covermode=atomic ${{ matrix.extra-arg }} \ -coverprofile=coverage.txt ./${{ matrix.pattern }} + - name: Convert GOCOVERDIR coverage data + run: go tool covdata textfmt -i=gocov -o vmintcoverage.txt + - uses: codecov/codecov-action@v4-beta env: CODECOV_TOKEN: '0ecf29bb-15fb-46f2-8c7d-0fb610d79f71' diff --git a/.vmtest.yaml b/.vmtest.yaml new file mode 100644 index 0000000000..7f32e6bc80 --- /dev/null +++ b/.vmtest.yaml @@ -0,0 +1,50 @@ +amd64: + VMTEST_QEMU: + container: "ghcr.io/hugelgupf/vmtest/qemu:main" + template: "{{.qemu}}/bin/qemu-system-x86_64 -L {{.qemu}}/pc-bios -m 1G" + directories: + qemu: "/zqemu" + + VMTEST_KERNEL: + container: "ghcr.io/u-root/u-root/test-kernel-amd64@sha256:10ea580ef29468f6d6f4674279586d37e118b145564fd1e99b708370262961a4" + template: "{{.bzImage}}" + files: + bzImage: "/bzImage" + + UROOT_MULTIBOOT_TEST_KERNEL_DIR: + container: "ghcr.io/u-root/u-root/test-multiboot-test-kernel-amd64@sha256:0fba729eddd76a50f5cfdfdabc4389d6a6902486b1190d354813ae953cc94b52" + template: "{{.mbdir}}" + directories: + mbdir: "/mb" + + UROOT_TEST_UEFIPAYLOAD: + container: "ghcr.io/u-root/u-root/test-uefipayload-amd64@sha256:4a9a47cdce32fb6dd7610f6de9f55d64b5dc380cf95bc23561696bb48e34622a" + template: "{{.payload}}" + files: + payload: "/UEFIPAYLOAD.fd" + +arm: + VMTEST_QEMU: + container: "ghcr.io/hugelgupf/vmtest/qemu:main" + template: "{{.qemu}}/bin/qemu-system-arm -M virt,highmem=off -L {{.qemu}}/pc-bios" + directories: + qemu: "/zqemu" + + VMTEST_KERNEL: + container: "ghcr.io/u-root/u-root/test-kernel-arm@sha256:d185d93812738b2869f4835d5a209fcf25ffdea4fd6601fcd206dfad67c9bced" + template: "{{.zImage}}" + files: + zImage: "/zImage" + +arm64: + VMTEST_QEMU: + container: "ghcr.io/hugelgupf/vmtest/qemu:main" + template: "{{.qemu}}/bin/qemu-system-aarch64 -machine virt -cpu max -m 1G -L {{.qemu}}/pc-bios" + directories: + qemu: "/zqemu" + + VMTEST_KERNEL: + container: "ghcr.io/u-root/u-root/test-kernel-arm64@sha256:805d64653b654ddf96e307cd3d7e835e5e9b9fd67d1b7e79b73cfa8ea2ce86e4" + template: "{{.Image}}" + files: + Image: "/Image" diff --git a/README.md b/README.md index be6cce5014..deb58581a5 100644 --- a/README.md +++ b/README.md @@ -77,17 +77,17 @@ u-root u-root core boot # Generate an archive with only these given commands -u-root ./cmds/core/{init,ls,ip,dhclient,wget,cat,elvish} +u-root ./cmds/core/{init,ls,ip,dhclient,wget,cat,gosh} # Generate an archive with all of the core tools with some exceptions u-root core -cmds/core/{ls,losetup} # Generate an archive with a tool outside of u-root git clone https://github.com/u-root/cpu -u-root ./cmds/core/{init,ls,elvish} ./cpu/cmds/cpud +u-root ./cmds/core/{init,ls,gosh} ./cpu/cmds/cpud # Generate an archive with a tool outside of u-root, in any PWD -(cd /tmp && GBB_PATH=$UROOT_PATH:$CPU_PATH u-root ./cmds/core/{init,ls,elvish} ./cmds/cpud) +(cd /tmp && GBB_PATH=$UROOT_PATH:$CPU_PATH u-root ./cmds/core/{init,ls,gosh} ./cmds/cpud) ``` The default set of packages included is all packages in @@ -100,12 +100,12 @@ checked for existence. For example: ```shell GBB_PATH=$HOME/u-root:$HOME/u-bmc u-root \ cmds/core/init \ - cmds/core/elvish \ + cmds/core/gosh \ cmd/socreset # matches: # $HOME/u-root/cmds/core/init -# $HOME/u-root/cmds/core/elvish +# $HOME/u-root/cmds/core/gosh # $HOME/u-bmc/cmd/socreset ``` @@ -153,7 +153,7 @@ symlink path. **Only `-uinitcmd` accepts command-line arguments, however.** For example, ```bash -u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,echo,elvish} +u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,echo,gosh} cpio -ivt < /tmp/initramfs.linux_amd64.cpio # ... @@ -178,7 +178,7 @@ Passing command line arguments like above is equivalent to passing the arguments Additionally, you can pass arguments to uinit via the `uroot.uinitargs` kernel parameters, for example: ```bash -u-root -uinitcmd="echo Gopher" ./cmds/core/{init,echo,elvish} +u-root -uinitcmd="echo Gopher" ./cmds/core/{init,echo,gosh} cpio -ivt < /tmp/initramfs.linux_amd64.cpio # ... @@ -205,7 +205,7 @@ The command you name must be present in the command set. The following will *not work*: ```bash -u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,elvish} +u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,gosh} # 2020/04/30 21:05:57 could not create symlink from "bin/uinit" to "echo": command or path "echo" not included in u-root build: specify -uinitcmd="" to ignore this error and build without a uinit ``` @@ -215,30 +215,30 @@ don't presume to know whether your symlink target is correct or not. This will build, but not work unless you add a /bin/foobar to the initramfs. ```bash -u-root -uinitcmd="/bin/foobar Go Gopher" ./cmds/core/{init,elvish} +u-root -uinitcmd="/bin/foobar Go Gopher" ./cmds/core/{init,gosh} ``` This will boot the same as the above. ```bash -u-root -uinitcmd="/bin/foobar Go Gopher" -files /bin/echo:bin/foobar -files your-hosts-file:/etc/hosts ./cmds/core/{init,elvish} +u-root -uinitcmd="/bin/foobar Go Gopher" -files /bin/echo:bin/foobar -files your-hosts-file:/etc/hosts ./cmds/core/{init,gosh} ``` The effect of the above command: * Sets up the uinit command to be /bin/foobar, with 2 arguments: Go Gopher * Adds /bin/echo as bin/foobar * Adds your-hosts-file as etc/hosts -* builds in the cmds/core/init, and cmds/core/elvish commands. +* builds in the cmds/core/init, and cmds/core/gosh commands. The {} are expanded by the shell This will bypass the regular u-root init and just launch a shell: ```bash -u-root -initcmd=elvish ./cmds/core/{elvish,ls} +u-root -initcmd=gosh ./cmds/core/{gosh,ls} cpio -ivt < /tmp/initramfs.linux_amd64.cpio # ... -# lrwxrwxrwx 0 root root 9 Dec 31 1969 init -> bbin/elvish +# lrwxrwxrwx 0 root root 9 Dec 31 1969 init -> bbin/gosh qemu-system-x86_64 -kernel $KERNEL -initrd /tmp/initramfs.linux_amd64.cpio -nographic -append "console=ttyS0" # ... diff --git a/cmds/core/cp/cp_test.go b/cmds/core/cp/cp_test.go index 4824b9003e..1ae0b18ee3 100644 --- a/cmds/core/cp/cp_test.go +++ b/cmds/core/cp/cp_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/u-root/u-root/pkg/cp" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) diff --git a/cmds/core/kexec/kexec_linux.go b/cmds/core/kexec/kexec_linux.go index fe316196da..3caa9e106d 100644 --- a/cmds/core/kexec/kexec_linux.go +++ b/cmds/core/kexec/kexec_linux.go @@ -27,7 +27,6 @@ package main import ( - "encoding/json" "io" "log" "os" @@ -41,24 +40,21 @@ import ( "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/boot/purgatory" "github.com/u-root/u-root/pkg/cmdline" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type options struct { - cmdline string - debug bool - dtb string - exec bool - extra string - initramfs string - load bool - loadSyscall bool - linuxImageCfgFile string - mmapInitrd bool - mmapKernel bool - modules []string - purgatory string - reuseCmdline bool + cmdline string + debug bool + dtb string + exec bool + extra string + initramfs string + load bool + loadSyscall bool + modules []string + purgatory string + reuseCmdline bool } func registerFlags() *options { @@ -73,9 +69,6 @@ func registerFlags() *options { flag.StringVar(&o.initramfs, "initramfs", "", "Use file as the kernel's initial ramdisk") flag.BoolVarP(&o.load, "load", "l", false, "Load the new kernel into the current kernel") flag.BoolVarP(&o.loadSyscall, "loadsyscall", "L", false, "Use the kexec_load syscall (not kexec_file_load)") - flag.StringVarP(&o.linuxImageCfgFile, "linux-image-cfg-file", "I", "", "Load Linux image from JSON info file given") - flag.BoolVar(&o.mmapInitrd, "mmap-initrd", true, "Mmap initrd file into virtual buffer, other than directly reading it (Only supported in Arm64 classic load mode for now)") - flag.BoolVar(&o.mmapKernel, "mmap-kernel", true, "Mmap kernel file into virtual buffer, other than directly reading it (Only supported in Arm64 classi load mode for now)") flag.StringArrayVar(&o.modules, "module", nil, `Load multiboot module with command line args (e.g --module="mod arg1")`) // This is broken out as it is almost never to be used. But it is valueable, nonetheless. @@ -97,33 +90,10 @@ func main() { if flag.NArg() > 0 { kernelpath = flag.Arg(0) } - // Option values from linux image config takes precedence - // over from cmdline. - if len(opts.linuxImageCfgFile) > 0 { - log.Printf("Load image info from %s", opts.linuxImageCfgFile) - d, err := os.ReadFile(opts.linuxImageCfgFile) - if err != nil { - log.Fatalf("Failed to load image info: %v", err) - } - lli := boot.LoadedLinuxImage{} - if err := json.Unmarshal(d, &lli); err != nil { - log.Fatalf("Unmarshal image info: %v", err) - } - if lli.Kernel != nil { - kernelpath = lli.Kernel.Name() - } - opts.cmdline = lli.Cmdline - if lli.Initrd != nil { - opts.initramfs = lli.Initrd.Name() - } - opts.loadSyscall = lli.LoadSyscall - opts.mmapKernel = lli.KexecOpts.MmapKernel - opts.mmapInitrd = lli.KexecOpts.MmapRamfs - } if (!opts.exec && len(kernelpath) == 0) || flag.NArg() > 1 { flag.PrintDefaults() - log.Fatalf("usage: kexec [flags] kernelname OR kexec [flags] -linux-image-cfg-file [file] OR kexec -e") + log.Fatalf("usage: kexec [flags] kernelname OR kexec -e") } if opts.cmdline != "" && opts.reuseCmdline { @@ -193,11 +163,7 @@ func main() { Initrd: i, Cmdline: newCmdline, LoadSyscall: opts.loadSyscall, - KexecOpts: linux.KexecOptions{ - DTB: dtb, - MmapKernel: opts.mmapKernel, - MmapRamfs: opts.mmapInitrd, - }, + DTB: dtb, } } if err := image.Load(boot.WithVerbose(opts.debug)); err != nil { diff --git a/cmds/core/nohup/nohup.go b/cmds/core/nohup/nohup.go new file mode 100644 index 0000000000..286ee1c8e3 --- /dev/null +++ b/cmds/core/nohup/nohup.go @@ -0,0 +1,85 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// nohup – invoke a utility immune to hangups. +// +// Synopsis: +// +// nohup [args...] +package main + +import ( + "errors" + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "syscall" + + "golang.org/x/term" +) + +var ( + errUsage = fmt.Errorf("nohup [args...]") + errStart = fmt.Errorf("failed to start") + errFinish = fmt.Errorf("finished with error") +) + +func main() { + if err := run(os.Args); err != nil { + if errors.Is(err, errUsage) { + fmt.Fprintf(os.Stderr, "Usage: %v\n", errUsage) + os.Exit(127) + } + log.Fatalf("nohup: %v", err) + } +} + +func run(args []string) error { + if len(args) < 2 { + return errUsage + } + + signal.Ignore(syscall.SIGHUP) + + cmdName := args[1] + cmdArgs := args[2:] + + cmd := exec.Command(cmdName, cmdArgs...) + + stdoutIsTerminal := term.IsTerminal(int(os.Stdout.Fd())) + stderrIsTerminal := term.IsTerminal(int(os.Stderr.Fd())) + + if stdoutIsTerminal { + outputFile, err := os.OpenFile("nohup.out", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("error opening file: %w", err) + } + defer outputFile.Close() + + cmd.Stdout = outputFile + + if stderrIsTerminal { + cmd.Stderr = outputFile + } else { + cmd.Stderr = os.Stderr + } + } else { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + + err := cmd.Start() + if err != nil { + return fmt.Errorf("%s: %w: %v", cmdName, errStart, err) + } + + err = cmd.Wait() + if err != nil { + return fmt.Errorf("%s: %w: %v", cmdName, errFinish, err) + } + + return nil +} diff --git a/cmds/core/nohup/nohup_test.go b/cmds/core/nohup/nohup_test.go new file mode 100644 index 0000000000..1e7c6c07cd --- /dev/null +++ b/cmds/core/nohup/nohup_test.go @@ -0,0 +1,71 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "golang.org/x/term" +) + +func TestRun(t *testing.T) { + tests := []struct { + err error + name string + output string + args []string + }{ + { + name: "no arguments", + args: []string{"nohup"}, + err: errUsage, + }, + { + name: "invalid command", + args: []string{"nohup", "invalidcommand"}, + err: errStart, + }, + { + name: "false command", + args: []string{"nohup", "false"}, + err: errFinish, + }, + { + name: "valid command", + args: []string{"nohup", "echo", "hello"}, + output: "hello\n", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dir := t.TempDir() + err := os.Chdir(dir) + if err != nil { + t.Fatalf("can't chdir into %q", dir) + } + + err = run(test.args) + + if !errors.Is(err, test.err) { + t.Errorf("expected %v, got %v", test.err, err) + } + + if test.output != "" && term.IsTerminal(int(os.Stdout.Fd())) { + b, err := os.ReadFile(filepath.Join(dir, "nohup.out")) + if err != nil { + t.Fatalf("can't open nohup.out: %v", err) + } + + if string(b) != test.output { + t.Errorf("expected %q, got %q", test.output, string(b)) + } + } + }) + } +} diff --git a/cmds/core/ping/ping.go b/cmds/core/ping/ping.go index fb3e05ffab..1ed5e14a7f 100644 --- a/cmds/core/ping/ping.go +++ b/cmds/core/ping/ping.go @@ -21,9 +21,10 @@ package main import ( - "encoding/binary" + "bytes" "flag" "fmt" + "io" "log" "math" "net" @@ -31,158 +32,142 @@ import ( "time" "github.com/u-root/u-root/pkg/uroot/util" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" ) const usage = "ping [-V] [-6] [-c count] [-i interval] [-s packetsize] [-w deadline] [-a audible] destination" -var ( - net6 = flag.Bool("6", false, "use ipv4 (means ip4:icmp) or 6 (ip6:ipv6-icmp)") - packetSize = flag.Int("s", 64, "Data size") - iter = flag.Uint64("c", math.MaxUint64, "# iterations") - intv = flag.Int("i", 1000, "interval in milliseconds") - wtf = flag.Int("w", 100, "wait time in milliseconds") - audible = flag.Bool("a", false, "Audible rings a bell when a packet is received") -) - -const ( - ICMP_TYPE_ECHO_REQUEST = 8 - ICMP_TYPE_ECHO_REPLY = 0 - ICMP_ECHO_REPLY_HEADER_IPV4_OFFSET = 20 -) - -const ( - ICMP6_TYPE_ECHO_REQUEST = 128 - ICMP6_TYPE_ECHO_REPLY = 129 - ICMP6_ECHO_REPLY_HEADER_IPV6_OFFSET = 40 -) - -type Ping struct { - dial func(string, string) (net.Conn, error) +type params struct { + packetSize int + intv int + wtf int + iter uint64 + host string + net6 bool + audible bool } -func New() *Ping { - return &Ping{ - dial: net.Dial, - } +type cmd struct { + stdout io.Writer + conn net.PacketConn + params } -func cksum(bs []byte) uint16 { - sum := uint32(0) - - for k := 0; k < len(bs)/2; k++ { - sum += uint32(bs[k*2]) << 8 - sum += uint32(bs[k*2+1]) - } - if len(bs)%2 != 0 { - sum += uint32(bs[len(bs)-1]) << 8 +func command(stdin io.Writer, p params) (*cmd, error) { + netname, address := "ip4:icmp", "0.0.0.0" + if p.net6 { + netname, address = "ip6:ipv6-icmp", "::1" } - sum = (sum >> 16) + (sum & 0xffff) - sum = (sum >> 16) + (sum & 0xffff) - if sum == 0xffff { - sum = 0 + conn, err := icmp.ListenPacket(netname, address) + if err != nil { + return nil, fmt.Errorf("can't setup %s socket on %s: %v", netname, address, err) } - return ^uint16(sum) + return &cmd{stdin, conn, p}, nil } -func (p *Ping) ping1(net6 bool, host string, i uint64, waitFor time.Duration) (string, error) { - netname := "ip4:icmp" - // todo: just figure out if it's an ip6 address and go from there. - if net6 { - netname = "ip6:ipv6-icmp" +func (c *cmd) run() error { + defer c.conn.Close() + if c.packetSize < 8 { + return fmt.Errorf("packet size too small (must be >= 8): %v", c.packetSize) + } + + network := "ip4" + if c.net6 { + network = "ip6" } - c, err := p.dial(netname, host) + + addr, err := net.ResolveIPAddr(network, c.host) if err != nil { - return "", fmt.Errorf("net.Dial(%v %v) failed: %v", netname, host, err) + return fmt.Errorf("failed to resolve address: %v", err) } - defer c.Close() - if net6 { - ipc := c.(*net.IPConn) - if err := setupICMPv6Socket(ipc); err != nil { - return "", fmt.Errorf("failed to set up the ICMPv6 connection: %w", err) + interval := time.Duration(c.intv) + waitFor := time.Duration(c.wtf) * time.Millisecond + for i := uint64(0); i < c.iter; i++ { + msg, err := c.ping(addr, i+1, waitFor) + if err != nil { + return fmt.Errorf("ping failed: %v", err) } + if c.audible { + msg = "\a" + msg + } + fmt.Fprintf(c.stdout, "%s\n", msg) + time.Sleep(time.Millisecond * interval) } - // Send ICMP Echo Request - c.SetDeadline(time.Now().Add(waitFor)) - msg := make([]byte, *packetSize) - if net6 { - msg[0] = ICMP6_TYPE_ECHO_REQUEST - } else { - msg[0] = ICMP_TYPE_ECHO_REQUEST - } - msg[1] = 0 - binary.BigEndian.PutUint16(msg[6:], uint16(i)) - binary.BigEndian.PutUint16(msg[4:], uint16(i>>16)) - binary.BigEndian.PutUint16(msg[2:], cksum(msg)) - if _, err := c.Write(msg[:]); err != nil { - return "", fmt.Errorf("write failed: %v", err) + return nil +} + +func (c *cmd) ping(addr *net.IPAddr, i uint64, waitFor time.Duration) (string, error) { + c.conn.SetDeadline(time.Now().Add(waitFor)) + + var echoRequestType icmp.Type = ipv4.ICMPTypeEcho + if c.net6 { + echoRequestType = ipv6.ICMPTypeEchoRequest } - // Get ICMP Echo Reply - c.SetDeadline(time.Now().Add(waitFor)) - rmsg := make([]byte, *packetSize+256) - before := time.Now() - amt, err := c.Read(rmsg[:]) - if err != nil { - return "", fmt.Errorf("read failed: %v", err) + wm := icmp.Message{Type: echoRequestType, Code: 0, Body: &icmp.Echo{ + ID: os.Getpid() & 0xffff, + Seq: int(i), + Data: bytes.Repeat([]byte{1}, c.packetSize)}, } - latency := time.Since(before) - if !net6 { - rmsg = rmsg[ICMP_ECHO_REPLY_HEADER_IPV4_OFFSET:] + wb, err := wm.Marshal(nil) + if err != nil { + return "", fmt.Errorf("icmp.Message.Marshal failed: %v", err) } - if net6 { - if rmsg[0] != ICMP6_TYPE_ECHO_REPLY { - return "", fmt.Errorf("bad ICMPv6 echo reply type, got %d, want %d", rmsg[0], ICMP6_TYPE_ECHO_REPLY) - } - } else { - if rmsg[0] != ICMP_TYPE_ECHO_REPLY { - return "", fmt.Errorf("bad ICMP echo reply type, got %d, want %d", rmsg[0], ICMP_TYPE_ECHO_REPLY) - } + + startTime := time.Now() + _, err = c.conn.WriteTo(wb, addr) + if err != nil { + return "", fmt.Errorf("conn.Write failed: %v", err) } - cks := binary.BigEndian.Uint16(rmsg[2:]) - binary.BigEndian.PutUint16(rmsg[2:], 0) - // only validate the checksum for IPv4. For IPv6 this *should* be done by the - // TCP stack (and do we need to validate the checksum anyway?) - if !net6 && cks != cksum(rmsg) { - return "", fmt.Errorf("bad ICMP checksum: %v (expected %v)", cks, cksum(rmsg)) + + rb := make([]byte, 1500) + n, _, err := c.conn.ReadFrom(rb) + if err != nil { + return "", fmt.Errorf("conn.Read failed: %v", err) } - id := binary.BigEndian.Uint16(rmsg[4:]) - seq := binary.BigEndian.Uint16(rmsg[6:]) - rseq := uint64(id)<<16 + uint64(seq) - if rseq != i { - return "", fmt.Errorf("wrong sequence number %v (expected %v)", rseq, i) + + latency := time.Since(startTime) + + var echoReplyType icmp.Type = ipv4.ICMPTypeEchoReply + if c.net6 { + echoReplyType = ipv6.ICMPTypeEchoReply } - return fmt.Sprintf("%d bytes from %v: icmp_seq=%v, time=%v", amt, host, i, latency), nil -} + msg, err := icmp.ParseMessage(echoReplyType.Protocol(), rb[:n]) + if err != nil { + return "", fmt.Errorf("icmp.ParseMessage failed: %v", err) + } -func ping(host string) error { - if *packetSize < 8 { - return fmt.Errorf("packet size too small (must be >= 8): %v", *packetSize) + echoReply, ok := msg.Body.(*icmp.Echo) + if !ok { + return "", fmt.Errorf("got %+v; want echo reply", msg) } - interval := time.Duration(*intv) - p := New() - // ping needs to run forever if count is not specified, so default value is MaxUint64 - waitFor := time.Duration(*wtf) * time.Millisecond - for i := uint64(0); i < *iter; i++ { - msg, err := p.ping1(*net6, host, i+1, waitFor) - if err != nil { - return fmt.Errorf("ping failed: %v", err) - } - if *audible { - msg = "\a" + msg - } - log.Print(msg) - time.Sleep(time.Millisecond * interval) + if echoReply.ID != os.Getpid()&0xffff { + return "", fmt.Errorf("got id %v; want %v", echoReply.ID, os.Getpid()&0xffff) + } + if echoReply.Seq != int(i) { + return "", fmt.Errorf("got seq %v; want %v", echoReply.Seq, i) } - return nil + return fmt.Sprintf("%d bytes from %v: icmp_seq=%v time=%v", n, c.host, i, latency), nil } func main() { + var ( + net6 = flag.Bool("6", false, "use ipv4 (means ip4:icmp) or 6 (ip6:ipv6-icmp)") + packetSize = flag.Int("s", 56, "Data size") + iter = flag.Uint64("c", math.MaxUint64, "# iterations") + intv = flag.Int("i", 1000, "interval in milliseconds") + wtf = flag.Int("w", 100, "wait time in milliseconds") + audible = flag.Bool("a", false, "Audible rings a bell when a packet is received") + ) + flag.Usage = util.Usage(flag.Usage, usage) flag.Parse() // options without parameters (right now just: -hV) @@ -191,7 +176,11 @@ func main() { os.Exit(1) } host := flag.Args()[0] - if err := ping(host); err != nil { + cmd, err := command(os.Stdout, params{*packetSize, *intv, *wtf, *iter, host, *net6, *audible}) + if err != nil { + log.Fatal(err) + } + if err := cmd.run(); err != nil { log.Fatal(err) } } diff --git a/cmds/core/ping/ping_linux.go b/cmds/core/ping/ping_linux.go deleted file mode 100644 index f53f5a2686..0000000000 --- a/cmds/core/ping/ping_linux.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -package main - -import ( - "fmt" - "net" - - "golang.org/x/sys/unix" -) - -func setupICMPv6Socket(c *net.IPConn) error { - file, err := c.File() - if err != nil { - return fmt.Errorf("net.IPConn.File failed: %w", err) - } - // we want the stack to return us the network error if any occurred - if err := unix.SetsockoptInt(int(file.Fd()), unix.SOL_IPV6, unix.IPV6_RECVERR, 1); err != nil { - return fmt.Errorf("failed to set sock opt IPV6_RECVERR: %w", err) - } - return nil -} diff --git a/cmds/core/ping/ping_other.go b/cmds/core/ping/ping_other.go deleted file mode 100644 index e98fb9ea49..0000000000 --- a/cmds/core/ping/ping_other.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !tinygo && !linux -// +build !tinygo,!linux - -package main - -import ( - "errors" - "net" -) - -func setupICMPv6Socket(c *net.IPConn) error { - return errors.New("setting up ICMPv6 socket only supported on Linux") -} diff --git a/cmds/core/ping/ping_test.go b/cmds/core/ping/ping_test.go index 19cbabb7e8..d2ea83c606 100644 --- a/cmds/core/ping/ping_test.go +++ b/cmds/core/ping/ping_test.go @@ -5,222 +5,199 @@ package main import ( - "fmt" + "bytes" "net" + "strconv" + "strings" "testing" "time" + + "github.com/hugelgupf/vmtest/guest" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" ) -type myConn struct { - net.IPConn - testRun string +type testConn struct { + lastMessage []byte } -func (M *myConn) Read(b []byte) (int, error) { - if M.testRun == "error in read" { - return 0, fmt.Errorf("err") +func (tc *testConn) ReadFrom(b []byte) (int, net.Addr, error) { + m, err := icmp.ParseMessage(ipv4.ICMPTypeEcho.Protocol(), tc.lastMessage) + if err != nil { + return 0, nil, err } - if M.testRun == "rmsg[0] != ICMP_TYPE_ECHO_REPLY" { - b[20] = 0xff + + body := m.Body.(*icmp.Echo) + + respone := icmp.Message{ + Type: ipv4.ICMPTypeEchoReply, + Code: 0, + Body: &icmp.Echo{ + ID: body.ID, + Seq: body.Seq, + Data: body.Data, + }, } - if M.testRun == "!net6 && cks != cksum(rmsg)" { - return 0, nil + + resp, err := respone.Marshal(nil) + if err != nil { + return 0, nil, err } - b[22] = 0xff - b[23] = 0xff - return 0, nil + + n := copy(b, resp) + return n, nil, nil } -func (M *myConn) Write(b []byte) (int, error) { - if M.testRun == "error in write" { - return 0, fmt.Errorf("err") - } - return 0, nil +func (tc *testConn) WriteTo(b []byte, addr net.Addr) (int, error) { + tc.lastMessage = b + return len(b), nil } -// Test cksum -func TestCkSum(t *testing.T) { - for _, tt := range []struct { - name string - input []byte - want uint16 - }{ - { - name: "ultimate test, triggers the sum == 0xffff and len(bs)%2 !=0", - input: []byte{0xff, 0xff, 0x00}, - want: 65535, - }, - { - name: "another input", - input: []byte{0xfe, 0xfe, 0xfe, 0xfe}, - want: 514, - }, - { - name: "empty input", - input: []byte{}, - want: 65535, - }, - } { - if got := cksum(tt.input); got != tt.want { - t.Errorf("cksum() = '%d', want: '%d'", got, tt.want) - } - } +func (tc *testConn) Close() error { + return nil +} +func (tc *testConn) LocalAddr() net.Addr { + return nil } -// Test Ping1 -func TestPing1(t *testing.T) { - for _, tt := range []struct { - name string - p Ping - net6 bool - host string - i uint64 - waitFor time.Duration - want error - }{ - { - name: "ping1 without error", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "ping1 without error"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf(""), - }, - { - name: "error in dial", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return nil, fmt.Errorf("err") - }, - }, - net6: false, - host: "test.com", - i: 1, - waitFor: time.Minute, - want: fmt.Errorf("net.Dial(%v %v) failed: %v", "ip4:icmp", "test.com", fmt.Errorf("err")), - }, - { - name: "error in write", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "error in write"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("write failed: err"), - }, - { - name: "error in read", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "error in read"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("read failed: err"), - }, - { - name: "rmsg[0] != ICMP_TYPE_ECHO_REPLY", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "rmsg[0] != ICMP_TYPE_ECHO_REPLY"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("bad ICMP echo reply type, got %d, want %d", 0xff, ICMP_TYPE_ECHO_REPLY), - }, - { - name: "!net6 && cks != cksum(rmsg)", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "!net6 && cks != cksum(rmsg)"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("bad ICMP checksum: %v (expected %v)", 0, 65535), - }, - { - name: "rseq != i", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "rseq != i"}, nil - }, - }, - net6: false, - host: "test.com", - i: 1, - waitFor: time.Minute, - want: fmt.Errorf("wrong sequence number %v (expected %v)", 0, 1), - }, - } { - t.Run(tt.name, func(t *testing.T) { - if _, got := tt.p.ping1(tt.net6, tt.host, tt.i, tt.waitFor); got != nil { - if got.Error() != tt.want.Error() { - t.Errorf("ping1() = '%s', want: '%s'", got, tt.want) - } +func (tc *testConn) SetDeadline(t time.Time) error { + return nil +} + +func (tc *testConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (tc *testConn) SetWriteDeadline(t time.Time) error { + return nil +} + +type pingOutputLine struct { + addr string + size int + seq int + audible bool +} + +func parsePingLines(t *testing.T, output []byte) []pingOutputLine { + t.Helper() + var lines []pingOutputLine + + for _, line := range bytes.Split(output, []byte("\n")) { + if len(line) == 0 { + continue + } + + var pl pingOutputLine + sp := strings.Split(string(line), " ") + if sp[0][0] == '\a' { + pl.audible = true + size, err := strconv.Atoi(sp[0][1:]) + if err != nil { + t.Fatalf("size atoi failed: %v", err) } - }) + pl.size = size + } else { + size, err := strconv.Atoi(sp[0]) + if err != nil { + t.Fatalf("size atoi failed: %v", err) + } + pl.size = size + } + + pl.addr = sp[3][:len(sp[3])-1] + sp = strings.Split(sp[4], "=") + sqn, err := strconv.Atoi(sp[1]) + if err != nil { + t.Fatalf("address atoi failed: %v", err) + } + pl.seq = sqn + + lines = append(lines, pl) } + + return lines } -// Test refactored ping() func TestPing(t *testing.T) { - for _, tt := range []struct { - name string - packetSize int - audible bool - host string - waitFor time.Duration - want error - }{ - { - name: "packetSize < 8", - packetSize: 7, - host: "test.com", - audible: true, - waitFor: time.Minute, - want: fmt.Errorf("packet size too small (must be >= 8): %v", 7), - }, - { - name: "ping with error", - packetSize: 8, - host: "", + var paConn net.PacketConn = &testConn{} + paConn.Close() + + stdout := &bytes.Buffer{} + cmd := &cmd{ + stdout: stdout, + conn: paConn, + params: params{ + host: "1.1.1.1", + packetSize: 56, + intv: 1000, + wtf: 100, + iter: 1, + net6: false, audible: true, - waitFor: time.Minute, - want: fmt.Errorf("ping failed: net.Dial(ip4:icmp ) failed: dial ip4:icmp: missing address"), }, - } { - t.Run(tt.name, func(t *testing.T) { - *packetSize = tt.packetSize - *audible = tt.audible - if got := ping(tt.host); got != nil { - if got.Error() != tt.want.Error() { - t.Errorf("ping() = '%s', want: '%s'", got, tt.want) - } - } - }) + } + + err := cmd.run() + if err != nil { + t.Error(err) + } + + lines := parsePingLines(t, stdout.Bytes()) + if len(lines) != 1 { + t.Errorf("expected 1 line, got %d", len(lines)) + } + + if lines[0].size != 64 { + t.Errorf("expected size 64 (56 + header), got %d", lines[0].size) + } + if !lines[0].audible { + t.Errorf("expected audible, got %v", lines[0].audible) + } + if lines[0].seq != 1 { + t.Errorf("expected seq 1, got %d", lines[0].seq) + } + if lines[0].addr != "1.1.1.1" { + t.Errorf("expected addr, got %s", lines[0].addr) } } -// This test gets the coverage higher and does not test any functionality. -func TestNew(t *testing.T) { - _ = New() +func TestRawPing(t *testing.T) { + guest.SkipIfNotInVM(t) + stdout := &bytes.Buffer{} + cmd, err := command(stdout, params{ + packetSize: 56, + host: "127.0.0.1", + intv: 1000, + wtf: 100, + iter: 1, + }) + if err != nil { + t.Errorf("command() failed: %v", err) + } + + err = cmd.run() + if err != nil { + t.Errorf("run() failed: %v", err) + } + + lines := parsePingLines(t, stdout.Bytes()) + + if len(lines) != 1 { + t.Errorf("expected 1 line, got %d", len(lines)) + } + + if lines[0].size != 64 { + t.Errorf("expected size 64 (56 + header), got %d", lines[0].size) + } + if lines[0].audible { + t.Errorf("expected no audible, got %v", lines[0].audible) + } + if lines[0].seq != 1 { + t.Errorf("expected seq 1, got %d", lines[0].seq) + } + if lines[0].addr != "127.0.0.1" { + t.Errorf("expected addr, got %s", lines[0].addr) + } } diff --git a/cmds/core/sshd/const_unix.go b/cmds/core/sshd/const_unix.go index ee50821725..23f8cf8acb 100644 --- a/cmds/core/sshd/const_unix.go +++ b/cmds/core/sshd/const_unix.go @@ -8,6 +8,6 @@ package main var ( - shells = [...]string{"bash", "zsh", "elvish"} + shells = [...]string{"bash", "zsh", "gosh", "elvish"} shell = "/bin/sh" ) diff --git a/cmds/core/wget/wget.go b/cmds/core/wget/wget.go index e59971d9a4..c9566e76f6 100644 --- a/cmds/core/wget/wget.go +++ b/cmds/core/wget/wget.go @@ -35,7 +35,7 @@ import ( "strings" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) var errEmptyURL = errors.New("empty url") diff --git a/cmds/exp/dumpmemmap/main_linux.go b/cmds/exp/dumpmemmap/main_linux.go new file mode 100644 index 0000000000..525d02c05a --- /dev/null +++ b/cmds/exp/dumpmemmap/main_linux.go @@ -0,0 +1,71 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Command dumpmemmap prints different kernel interpretations of physical +// memory address space. +// +// Support for: +// +// - /proc/iomem (exists on all systems) +// - /sys/firmware/memmap (exists on x86 systems) +// - /sys/kernel/debug/memblock (exists on systems with CONFIG_ARCH_KEEP_MEMBLOCK, in particular arm64) +// - /sys/firmware/fdt (exists on systems with device trees) +package main + +import ( + "fmt" + "log" + + "github.com/dustin/go-humanize" + "github.com/u-root/u-root/pkg/boot/kexec" + "github.com/u-root/u-root/pkg/dt" +) + +func printMM(mm kexec.MemoryMap) { + for _, r := range mm { + fmt.Println(" ", r, " ", humanize.Bytes(uint64(r.Range.Size))) + } +} + +func main() { + memmap, err := kexec.MemoryMapFromSysfsMemmap() + if err != nil { + log.Printf("/sys/firmware/memmap: %v", err) + } else { + fmt.Println("/sys/firmware/memmap:") + printMM(memmap) + } + + memblock, err := kexec.MemoryMapFromMemblock() + if err != nil { + log.Printf("/sys/kernel/debug/memblock: %v", err) + } else { + fmt.Println("/sys/kernel/debug/memblock:") + printMM(memblock) + } + + iomem, err := kexec.MemoryMapFromIOMem() + if err != nil { + log.Printf("/proc/iomem: %v", err) + } else { + fmt.Println("/proc/iomem:") + printMM(iomem) + } + + fdt, err := dt.LoadFDT(nil) + if err != nil { + log.Printf("loadFDT: %v", err) + return + } + + // Prepare segments. + fdtMap, err := kexec.MemoryMapFromFDT(fdt) + if err != nil { + log.Printf("MemoryMapFromFDT(%v): %v", fdt, err) + return + } + + fmt.Println("/sys/firmware/fdt:") + printMM(fdtMap) +} diff --git a/cmds/exp/madeye/madeye.go b/cmds/exp/madeye/madeye.go index 53d923b277..d72588f53b 100644 --- a/cmds/exp/madeye/madeye.go +++ b/cmds/exp/madeye/madeye.go @@ -39,7 +39,7 @@ import ( "path/filepath" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) diff --git a/cmds/exp/syscallfilter/main_linux.go b/cmds/exp/syscallfilter/main_linux.go index 001e3c464d..a4578f7ea3 100644 --- a/cmds/exp/syscallfilter/main_linux.go +++ b/cmds/exp/syscallfilter/main_linux.go @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !tinygo && linux && amd64 -// +build !tinygo,linux,amd64 +//go:build !tinygo && ((linux && arm64) || (linux && amd64) || (linux && riscv64)) +// +build !tinygo +// +build linux,arm64 linux,amd64 linux,riscv64 // syscallfilter runs a command with a possibly empty set of filters: // diff --git a/cmds/exp/uefiboot/main.go b/cmds/exp/uefiboot/main.go index 385bb31d5d..fbd38dc6fd 100644 --- a/cmds/exp/uefiboot/main.go +++ b/cmds/exp/uefiboot/main.go @@ -33,6 +33,7 @@ var ( serialWidth = flag.Uint("serial_width", 1, "Serial port reg width") serialHertz = flag.Uint("serial_hertz", 1843200, "Serial port input hertz") serialBaud = flag.Uint("serial_baud", 115200, "Serial port baud rate") + execute = flag.Bool("e", true, "If true, kexec. If not true, just load kernel.") ) var v = func(string, ...interface{}) {} @@ -61,7 +62,9 @@ func main() { log.Fatal(err) } - if err := boot.Execute(); err != nil { - log.Fatal(err) + if *execute { + if err := boot.Execute(); err != nil { + log.Fatal(err) + } } } diff --git a/cmds/fwtools/flash/flash_linux.go b/cmds/fwtools/flash/flash_linux.go index 1c23671f3b..50f04becc3 100644 --- a/cmds/fwtools/flash/flash_linux.go +++ b/cmds/fwtools/flash/flash_linux.go @@ -10,6 +10,8 @@ // // Options: // +// -o offset: Offset at which to start. +// -s size: Number of bytes to read or write. // -p PROGRAMMER: Specify the programmer with zero or more parameters (see // below). // -r FILE: Read flash data into the file. @@ -43,6 +45,7 @@ import ( "fmt" "io" "log" + "math" "os" "sort" "strings" @@ -53,6 +56,7 @@ import ( type programmer interface { io.ReaderAt io.WriterAt + EraseAt(int64, int64) (int64, error) Size() int64 Close() error } @@ -94,9 +98,12 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr // Parse args. fs := flag.NewFlagSet("flash", flag.ContinueOnError) var ( - p = fs.StringP("programmer", "p", "", fmt.Sprintf("programmer (%s)", strings.Join(programmerList, ","))) - r = fs.StringP("read", "r", "", "read flash data into the file") - w = fs.StringP("write", "w", "", "write the file to flash") + e = fs.BoolP("erase", "e", false, "erase the flash part") + p = fs.StringP("programmer", "p", "", fmt.Sprintf("programmer (%s)", strings.Join(programmerList, ","))) + r = fs.StringP("read", "r", "", "read flash data into the file") + w = fs.StringP("write", "w", "", "write the file to flash") + off = fs.Int64P("offset", "o", 0, "off at which to write") + size = fs.Int64P("size", "s", math.MaxInt64, "number of bytes") ) if err := fs.Parse(args); err != nil { return err @@ -110,8 +117,8 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr return errors.New("-p needs to be set") } - if *r == "" && *w == "" { - return errors.New("either -r or -w need to be set") + if *r == "" && *w == "" && !*e { + return errors.New("at least one of -e, -r or -w need to be set") } if *r != "" && *w != "" { return errors.New("both -r and -w cannot be set") @@ -134,10 +141,18 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr } }() + if *e { + n, err := programmer.EraseAt(min(*size, programmer.Size()), *off) + if err != nil { + log.Fatal(err) + } + log.Printf("Erased %#x bytes @ %#x", n, *off) + } + // Create a buffer to hold the contents of the image. - buf := make([]byte, programmer.Size()) if *r != "" { + buf := make([]byte, min(*size, programmer.Size())) f, err := os.Create(*r) if err != nil { return err @@ -148,28 +163,27 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr reterr = err } }() - if _, err := programmer.ReadAt(buf, 0); err != nil { + if _, err := programmer.ReadAt(buf, *off); err != nil { return err } if _, err := f.Write(buf); err != nil { return err } } else if *w != "" { - f, err := os.Open(*w) + buf, err := os.ReadFile(*w) if err != nil { return err } - defer f.Close() - if _, err := io.ReadFull(f, buf); err != nil { - return err + buf = buf[:min(int64(len(buf)), *size)] + amt, err := programmer.WriteAt(buf, *off) + if err != nil { + return fmt.Errorf("writing %d bytes to dev %v:%w", len(buf), programmer, err) } - if leftover, err := io.Copy(io.Discard, f); err != nil { - return err - } else if leftover != 0 { - return fmt.Errorf("flash size (%#x) unequal to file size (%#x)", len(buf), int64(len(buf))+leftover) + if amt != len(buf) { + return fmt.Errorf("only flashed %d of %d bytes", amt, len(buf)) } - return errors.New("write not yet supported") + return nil } return nil diff --git a/cmds/fwtools/flash/spi_linux.go b/cmds/fwtools/flash/spi_linux.go index 50442f7785..3ab5935540 100644 --- a/cmds/fwtools/flash/spi_linux.go +++ b/cmds/fwtools/flash/spi_linux.go @@ -6,6 +6,7 @@ package main import ( "fmt" + "log" "math" "strconv" @@ -30,7 +31,7 @@ func init() { } delete(params, "dev") - spi, err := spidev.Open(dev) + spi, err := spidev.Open(dev, spidev.WithLogger(log.Printf)) if err != nil { return nil, err } diff --git a/cmds/fwtools/spidev/spidev_linux.go b/cmds/fwtools/spidev/spidev_linux.go index 191ef0aa16..d55a93de9d 100644 --- a/cmds/fwtools/spidev/spidev_linux.go +++ b/cmds/fwtools/spidev/spidev_linux.go @@ -13,7 +13,7 @@ // Options: // // -D DEV: spidev device (default /dev/spidev0.0) -// -s SPEED: max speed in Hz (default 500000) +// -s SPEED: max speed in Hz (default whatever the spi package sets) // // Description: // @@ -32,6 +32,7 @@ import ( flag "github.com/spf13/pflag" "github.com/u-root/u-root/pkg/flash" + "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" "github.com/u-root/u-root/pkg/flash/sfdp" "github.com/u-root/u-root/pkg/spidev" @@ -39,6 +40,8 @@ import ( type spi interface { Transfer([]spidev.Transfer) error + ID() (chips.ID, error) + Status() (op.Status, error) SetSpeedHz(uint32) error Close() error } @@ -58,7 +61,7 @@ func run(args []string, spiOpen spiOpenFunc, input io.Reader, output io.Writer) // Parse args. fs := flag.NewFlagSet("spidev", flag.ContinueOnError) dev := fs.StringP("device", "D", "/dev/spidev0.0", "spidev device") - speed := fs.Uint32P("speed", "s", 500000, "max speed in Hz") + speed := fs.Uint32P("speed", "s", 0, "max speed in Hz") if err := fs.Parse(args); err != nil { if err == flag.ErrHelp { return fmt.Errorf("%w:", errCommand) @@ -76,35 +79,26 @@ func run(args []string, spiOpen spiOpenFunc, input io.Reader, output io.Writer) return err } defer s.Close() - if err := s.SetSpeedHz(*speed); err != nil { - return err + + // Note that spidev.Open sets a safe default speed, known to + // work, that is conservative. In some cases, users might wish + // to override that speed. Since the speed can be set any number + // of times, this is a safe operation. + if *speed != 0 { + if err := s.SetSpeedHz(*speed); err != nil { + return err + } } cmd := fs.Arg(0) // Currently, only the raw subcommand is supported. switch cmd { case "id": - // Wake it up, then get the id. - // PRDRES is not universally handled on all devices, but that's ok. - // but CE MUST drop, so we structure this as two separate - // transfers to ensure that happens. - transfers := []spidev.Transfer{ - { - Tx: []byte{op.PRDRES}, - Rx: make([]byte, 1), - CSChange: true, - }, - { - Tx: []byte{op.ReadJEDECID, 0, 0, 0}, - Rx: make([]byte, 4), - }, - } - - if err := s.Transfer(transfers); err != nil { + id, err := s.ID() + if err != nil { return err } - - fmt.Fprintf(output, "%02x\n", transfers[1].Rx[1:]) + fmt.Fprintf(output, "%02x\n", id) return nil case "raw": diff --git a/cmds/fwtools/spidev/spidev_linux_test.go b/cmds/fwtools/spidev/spidev_linux_test.go index 9ee3990d6e..8d14334bc6 100644 --- a/cmds/fwtools/spidev/spidev_linux_test.go +++ b/cmds/fwtools/spidev/spidev_linux_test.go @@ -33,7 +33,7 @@ func TestRun(t *testing.T) { { name: "id", args: []string{"id"}, - wantOutputRegex: regexp.MustCompile("[0-9a-fA-F]{6}\n"), + wantOutputRegex: regexp.MustCompile("[0-9a-fA-F]*\n"), }, { name: "id failing IO", @@ -71,7 +71,7 @@ func TestRun(t *testing.T) { }, { name: "setspeedhz error", - args: []string{"raw"}, + args: []string{"-s", "1", "raw"}, input: []byte("abcd"), ForceSetSpeedHzErr: os.ErrInvalid, err: os.ErrInvalid, @@ -119,7 +119,7 @@ func TestRun(t *testing.T) { got := run(tt.args, openFakeSpi, bytes.NewBuffer(tt.input), output) if !errors.Is(got, tt.err) { - t.Errorf("run(): %v != %v", got, tt.err) + t.Fatalf("run(): %v != %v", got, tt.err) } gotOutputString := output.String() diff --git a/go.mod b/go.mod index 9928e9a1a2..31f5a2a1b5 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/go-tpm v0.9.1-0.20230914180155-ee6cbcd136f8 github.com/google/uuid v1.3.0 - github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f + github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 github.com/kevinburke/ssh_config v1.1.0 @@ -34,11 +34,13 @@ require ( github.com/spf13/pflag v1.0.5 github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a - github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 + github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e github.com/ulikunitz/xz v0.5.11 github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810 golang.org/x/crypto v0.17.0 + golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 + golang.org/x/net v0.19.0 golang.org/x/sys v0.15.0 golang.org/x/term v0.15.0 golang.org/x/text v0.14.0 @@ -56,7 +58,7 @@ require ( github.com/charmbracelet/bubbles v0.15.1-0.20230123181021-a6a12c4a31eb // indirect github.com/charmbracelet/bubbletea v0.24.1 // indirect github.com/charmbracelet/lipgloss v0.7.1 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 // indirect github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -80,9 +82,7 @@ require ( github.com/sahilm/fuzzy v0.1.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect golang.org/x/arch v0.2.0 // indirect - golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index c1c977475e..8987918c44 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= @@ -94,8 +94,8 @@ github.com/hexdigest/gowrap v1.1.7/go.mod h1:Z+nBFUDLa01iaNM+/jzoOA1JJ7sm51rnYFa github.com/hexdigest/gowrap v1.1.8/go.mod h1:H/JiFmQMp//tedlV8qt2xBdGzmne6bpbaSuiHmygnMw= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= -github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f h1:ov45/OzrJG8EKbGjn7jJZQJTN7Z1t73sFYNIRd64YlI= -github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f/go.mod h1:JoDrYMZpDPYo6uH9/f6Peqms3zNNWT2XiGgioMOIGuI= +github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa h1:2q0UvEA7TSTrjU4aFrpF6u28tHat3KnCkqsy/gB86v0= +github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa/go.mod h1:Z22CpRFjhoR/NoxBeEuyeGTwMC7G5s4RfKa9Bs/j74w= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 h1:h+RKaNPjka7LRJGoeub/IQBdXSoEaJjfADkBq02hvjw= @@ -174,8 +174,6 @@ github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4AN github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nanmu42/limitio v1.0.0 h1:dpopBYPwUyLOPv+vsGja0iax+dG0SP9paTEmz+Sy7KU= github.com/nanmu42/limitio v1.0.0/go.mod h1:8H40zQ7pqxzbwZ9jxsK2hDoE06TH5ziybtApt1io8So= -github.com/ncruces/go-fs v0.2.2 h1:ak7h7jdihotXtXqjrBb2YZViJ+n41tLIqMG9ZY7bJMQ= -github.com/ncruces/go-fs v0.2.2/go.mod h1:07xkoGj//ID8iICNv3rcD2PtMjia3mABv1yZzdq7qZ8= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/orangecms/go-framebuffer v0.0.0-20200613202404-a0700d90c330 h1:zJBTzBuTR7EdFzmCGx0xp0pbOOb82sAh0+YUK4JTDEI= github.com/orangecms/go-framebuffer v0.0.0-20200613202404-a0700d90c330/go.mod h1:3Myb/UszJY32F2G7yGkUtcW/ejHpjlGfYLim7cv2uKA= @@ -225,8 +223,8 @@ github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa h1:unMPGGK/CR github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa/go.mod h1:Zj4Tt22fJVn/nz/y6Ergm1SahR9dio1Zm/D2/S0TmXM= github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a h1:A0sK7WEodak7eVd21MOEatnh2pfAAwZaEPSIEEsjctQ= github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a/go.mod h1:RWIgJWqm9/0gjBZ0Hl8iR6MVGzZ+yAda2uqqLmetE2I= -github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg= -github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= +github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= diff --git a/integration/GET_KERNEL_QEMU b/integration/GET_KERNEL_QEMU deleted file mode 100755 index 09032de719..0000000000 --- a/integration/GET_KERNEL_QEMU +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash - -# This script is intended to run the tests we run at circleci, -# precisely as they are run there. -# -# to do so, it: -# o creates a directory to store local artifacts retrieved from docker -# see TMP= below -# o runs the standard test container to retrieve a the qemu, kernel, and bios image -# o runs go test with a default set of tests (./...) -# -# NOTE: if you want more complex behavior, don't make this script more -# complex. Convert it to Go. Complex shell scripts suck. - -# These docker artifacts should not persist. Place them in tmp. -# tmp is in .gitignore -# I would prefer /tmp/$$. -# Docker really doesn't like this for some reason, even when -# I map it to /out inside the container. -TMP=`pwd`/tmp -mkdir -p $TMP -chmod 777 $TMP - -# The default value is amd64, but you can override it, e.g. -# UROOT_TESTARCH=arm64 bash RUNLOCAL -export UROOT_TESTARCH=${UROOT_TESTARCH:=amd64} - -case $UROOT_TESTARCH in - - "amd64") - export UROOT_QEMU="qemu-system-x86_64" - export UROOT_QEMU_OPTS="-L $TMP/pc-bios -m 1G" - export UROOT_KERNEL=bzImage - export UROOT_BIOS=pc-bios - ;; - - "arm64") - export UROOT_QEMU=qemu-system-aarch64 - export UROOT_KERNEL=Image - export UROOT_BIOS="" - export UROOT_QEMU_OPTS="" - ;; - - "arm") - export UROOT_QEMU=qemu-system-arm - export UROOT_KERNEL=zImage - export UROOT_BIOS="" - export UROOT_QEMU_OPTS='-M virt -nographic' - export UROOT_QEMU_TIMEOUT_X=10 - - ;; - - *) - echo "$UROOT_TESTARCH is not a supported architecture (amd64, arm64, arm)" - exit 1 - ;; - -esac - -# We no longer allow you to pick a kernel to run. -# Since we wish to exactly mirror what circleci does, we always use the -# kernel and qemu in the container. -# Note the docker pull only hurts a lot the first time. -# After you have run it once, further cp operations take a second or so. -# By doing it this way, we always use the latest Docker files. -CONTAINER=uroottest/test-image-${UROOT_TESTARCH} - -DIR=/home/circleci - -docker run $CONTAINER bash -c "echo \$UROOT_QEMU" -docker run $CONTAINER tar Ccf $DIR - $UROOT_KERNEL $UROOT_QEMU $UROOT_BIOS | tar Cxf $TMP - - -ls -l $TMP - -# now adjust paths and such -export UROOT_KERNEL=$TMP/$UROOT_KERNEL -export UROOT_QEMU="$TMP/$UROOT_QEMU $UROOT_QEMU_OPTS" -export UROOT_BIOS=$TMP/$UROOT_BIOS diff --git a/integration/RUNLOCAL b/integration/RUNLOCAL deleted file mode 100755 index dc1cf3b816..0000000000 --- a/integration/RUNLOCAL +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# This script is intended to run, locally, the tests we run at circleci, -# precisely as they are run there. -# -# to do so, it: -# o creates a directory to store local artifacts retrieved from docker -# see TMP= below -# o runs a the standard test container to retrieve a the qemu, kernel, and bios image -# o runs go test with a default set of tests (./...) -# -# NOTE: if you want more complex behavior, don't make this script more -# complex. Convert it to Go. Complex shell scripts suck. - -# Take a guess: they are likely running it in this directory -UROOT_SOURCE=${UROOT_SOURCE:-${PWD}/..} -export UROOT_SOURCE - -set -e -set -x - -. GET_KERNEL_QEMU - -go test "$@" ./... diff --git a/integration/generic-tests/basic_test.go b/integration/generic-tests/basic_test.go index 26ca1b4fc5..27a348741e 100644 --- a/integration/generic-tests/basic_test.go +++ b/integration/generic-tests/basic_test.go @@ -13,21 +13,11 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/u-root/u-root/pkg/uroot" ) func TestScript(t *testing.T) { - testCmds := []string{ - "echo HELLO WORLD", - "shutdown -h", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/shutdown", - )}), - vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second)), - ) - + script := "echo HELLO WORLD" + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second))) if _, err := vm.Console.ExpectString("HELLO WORLD"); err != nil { t.Errorf("Want HELLO WORLD: %v", err) } diff --git a/integration/generic-tests/dhclient_test.go b/integration/generic-tests/dhclient_test.go index 17f99a38f8..592f1c5763 100644 --- a/integration/generic-tests/dhclient_test.go +++ b/integration/generic-tests/dhclient_test.go @@ -18,15 +18,13 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/hugelgupf/vmtest/qemu/network" + "github.com/hugelgupf/vmtest/qemu/qnetwork" "github.com/u-root/u-root/pkg/testutil" "github.com/u-root/u-root/pkg/uroot" ) // TestDhclientQEMU4 uses QEMU's DHCP server to test dhclient. func TestDhclientQEMU4(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - // Create the file to download dir := t.TempDir() want := "Hello, world!" @@ -45,30 +43,24 @@ func TestDhclientQEMU4(t *testing.T) { s := &http.Server{} port := ln.Addr().(*net.TCPAddr).Port - testCmds := []string{ - "dhclient -ipv6=false -v", - "ip a", - // Download a file to make sure dhclient configures kernel networking correctly. - fmt.Sprintf("wget http://192.168.0.2:%d/foobar", port), - "cat ./foobar", - "sleep 5", - "shutdown -h", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := fmt.Sprintf(` + dhclient -ipv6=false -v + ip a + wget http://192.168.0.2:%d/foobar + cat ./foobar + sleep 5 + `, port) + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/cat", "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/ip", "github.com/u-root/u-root/cmds/core/sleep", - "github.com/u-root/u-root/cmds/core/shutdown", "github.com/u-root/u-root/cmds/core/wget", )}), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), - network.IPv4HostNetwork(&net.IPNet{ - IP: net.IP{192, 168, 0, 0}, - Mask: net.CIDRMask(24, 32), - }), + qnetwork.IPv4HostNetwork("192.168.0.0/24"), qemu.ServeHTTP(s, ln), ), ) @@ -86,21 +78,17 @@ func TestDhclientQEMU4(t *testing.T) { } func TestDhclientTimesOut(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - - testCmds := []string{ - "dhclient -v -retry 2 -timeout 10", - "echo \"DHCP timed out\"", - "sleep 5", - "shutdown -h", - } - - net := network.NewInterVM() - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := ` + dhclient -v -retry 2 -timeout 10 + echo "DHCP timed out" + sleep 5 + ` + + net := qnetwork.NewInterVM() + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/sleep", - "github.com/u-root/u-root/cmds/core/shutdown", )}), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), @@ -125,19 +113,17 @@ func TestDhclientTimesOut(t *testing.T) { } func TestDhclient6(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - - serverCmds := []string{ - "ip link set eth0 up", - "pxeserver -6 -your-ip6=fec0::3 -4=false", - } + serverScript := ` + ip link set eth0 up + pxeserver -6 -your-ip6=fec0::3 -4=false + ` // QEMU doesn't support DHCPv6 for getting IP configuration, so we have // to supply our own server. // // We don't currently have a radvd server we can use, so we also cannot // try to download a file using the DHCP configuration. - net := network.NewInterVM() - serverVM := vmtest.StartVMAndRunCmds(t, serverCmds, + net := qnetwork.NewInterVM() + serverVM := vmtest.StartVMAndRunCmds(t, serverScript, vmtest.WithName("TestDhclient6_Server"), vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/ip", @@ -149,17 +135,15 @@ func TestDhclient6(t *testing.T) { ), ) - testCmds := []string{ - "dhclient -ipv4=false -vv", - "ip a", - "shutdown -h", - } - clientVM := vmtest.StartVMAndRunCmds(t, testCmds, + clientScript := ` + dhclient -ipv4=false -vv + ip a + ` + clientVM := vmtest.StartVMAndRunCmds(t, clientScript, vmtest.WithName("TestDhclient6_Client"), vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/ip", "github.com/u-root/u-root/cmds/core/dhclient", - "github.com/u-root/u-root/cmds/core/shutdown", )}), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), diff --git a/integration/generic-tests/esxi_boot_test.go b/integration/generic-tests/esxi_boot_test.go index e165c73336..5416b7ffb0 100644 --- a/integration/generic-tests/esxi_boot_test.go +++ b/integration/generic-tests/esxi_boot_test.go @@ -25,10 +25,8 @@ func TestESXi(t *testing.T) { t.Skip("ESXi disk image is not present. Please set UROOT_ESXI_IMAGE= to an installed ESXi disk image.") } - testCmds := []string{ - `esxiboot -d="/dev/sda" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"`, - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := `esxiboot -d="/dev/sda" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/exp/esxiboot", )}), @@ -79,10 +77,8 @@ func TestESXiNVMe(t *testing.T) { t.Skip("ESXi disk image is not present. Please set UROOT_ESXI_IMAGE= to an installed ESXi disk image.") } - testCmds := []string{ - `esxiboot -d="/dev/nvme0n1" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"`, - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := `esxiboot -d="/dev/nvme0n1" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/exp/esxiboot", )}), diff --git a/integration/generic-tests/io_test.go b/integration/generic-tests/io_test.go index 9b2517afae..57e45bdc87 100644 --- a/integration/generic-tests/io_test.go +++ b/integration/generic-tests/io_test.go @@ -9,27 +9,25 @@ package integration import ( "fmt" + "strings" "testing" "time" "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/u-root/u-root/pkg/uroot" ) // TestIO tests the string "UART TEST" is written to the serial port on 0x3f8. func TestIO(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) - testCmds := []string{} + testCmd := []string{"io"} for _, b := range []byte("UART TEST\r\n") { - testCmds = append(testCmds, fmt.Sprintf("io outb 0x3f8 %d", b)) + testCmd = append(testCmd, fmt.Sprintf("outb 0x3f8 %d", b)) } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/io", - )}), + vm := vmtest.StartVMAndRunCmds(t, strings.Join(testCmd, " "), + vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/core/io"), vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second)), ) @@ -45,15 +43,9 @@ func TestIO(t *testing.T) { func TestCMOS(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) - testCmds := []string{ - "io cw 14 1 cr 14 cw 14 0 cr 14", - "shutdown -h", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/io", - "github.com/u-root/u-root/cmds/core/shutdown", - )}), + script := "io cw 14 1 cr 14 cw 14 0 cr 14" + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/core/io"), vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second)), ) diff --git a/integration/generic-tests/kexec_test.go b/integration/generic-tests/kexec_test.go index a0ee05fcdd..4f331035de 100644 --- a/integration/generic-tests/kexec_test.go +++ b/integration/generic-tests/kexec_test.go @@ -11,14 +11,13 @@ import ( "fmt" "os" "os/exec" - "path/filepath" "testing" "time" "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" "github.com/hugelgupf/vmtest/testtmp" - "github.com/u-root/u-root/pkg/uroot" + "github.com/u-root/gobusybox/src/pkg/golang" ) // TestMountKexec tests that kexec occurs correctly by checking the kernel cmdline. @@ -27,24 +26,28 @@ import ( func TestMountKexec(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "var SUFFIX = $CMDLINE[-7..]", - "echo SAW $SUFFIX", - "kexec -i /testdata/initramfs.cpio -c $CMDLINE' KEXEC=Y' /kernel", - } - - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/kexec", - "github.com/u-root/u-root/cmds/core/shutdown", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - }, - }), + script := ` + CMDLINE=$(cat /proc/cmdline) + SUFFIX=${CMDLINE:(-7)} + echo SAW $SUFFIX + kexec -l -i /testdata/initramfs.cpio -c "${CMDLINE} KEXEC=Y" /kernel + sync + kexec -e + ` + + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + "github.com/u-root/u-root/cmds/core/sync", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles( + fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), + ), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -52,6 +55,10 @@ func TestMountKexec(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("SAW KEXEC=Y"); err != nil { @@ -73,24 +80,28 @@ func TestMountKexecLoad(t *testing.T) { t.Skipf("no gzip found, skip it as it won't be able to de-compress kernel") } - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "var SUFFIX = $CMDLINE[-7..]", - "echo SAW $SUFFIX", - "kexec -d -i /testdata/initramfs.cpio --loadsyscall -c $CMDLINE' KEXEC=Y' /kernel", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/kexec", - "github.com/u-root/u-root/cmds/core/shutdown", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - gzipP, - }, - }), + script := ` + CMDLINE=$(cat /proc/cmdline) + SUFFIX=${CMDLINE:(-7)} + echo SAW $SUFFIX + kexec -l -d -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE} KEXEC=Y" /kernel + sync + kexec -e + ` + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + "github.com/u-root/u-root/cmds/core/sync", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles( + fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), + gzipP, + ), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -98,6 +109,10 @@ func TestMountKexecLoad(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("SAW KEXEC=Y"); err != nil { @@ -118,21 +133,24 @@ func TestMountKexecLoadOnly(t *testing.T) { t.Skipf("no gzip found, skip it as it won't be able to de-compress kernel") } - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "echo kexecloadresult ?(kexec -d -l -i /testdata/initramfs.cpio --loadsyscall -c $CMDLINE /kernel)", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/kexec", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - gzipP, - }, - }), + script := ` + CMDLINE=$(cat /proc/cmdline) + kexec -d -l -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE}" /kernel + echo kexecloadresult $? + ` + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles( + fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), + gzipP, + ), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -140,9 +158,13 @@ func TestMountKexecLoadOnly(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) - if _, err := vm.Console.ExpectString("kexecloadresult $ok"); err != nil { + if _, err := vm.Console.ExpectString("kexecloadresult 0"); err != nil { t.Error(err) } if err := vm.Wait(); err != nil { @@ -154,24 +176,24 @@ func TestMountKexecLoadOnly(t *testing.T) { func TestMountKexecLoadCustomDTB(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchArm64) - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "var SUFFIX = $CMDLINE[-7..]", - "echo SAW $SUFFIX", - "cp /sys/firmware/fdt /tmp/userfdt", - "kexec -d --dtb /tmp/userfdt -i /testdata/initramfs.cpio --loadsyscall -c $CMDLINE' KEXEC=Y' /kernel", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/cp", - "github.com/u-root/u-root/cmds/core/kexec", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - }, - }), + script := ` + CMDLINE=$(cat /proc/cmdline) + SUFFIX=${CMDLINE:(-7)} + echo SAW $SUFFIX + cp /sys/firmware/fdt /tmp/userfdt + kexec -d --dtb /tmp/userfdt -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE} KEXEC=Y" /kernel + ` + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + "github.com/u-root/u-root/cmds/core/cp", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles(fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL"))), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -179,6 +201,10 @@ func TestMountKexecLoadCustomDTB(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("SAW KEXEC=Y"); err != nil { @@ -189,45 +215,3 @@ func TestMountKexecLoadCustomDTB(t *testing.T) { } _ = vm.Wait() } - -func TestKexecLinuxImageCfgFile(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - - dir := t.TempDir() - cfg := []byte("{ \"InitrdPath\": \"/testdata/initramfs.cpio\", \"KernelPath\": \"/kernel\", \"Cmdline\": \"/proc/cmdline\", \"Name\": \"testloadconfig\" }") - cfgFile := filepath.Join(dir, "linux_image_cfg.json") - if err := os.WriteFile(cfgFile, cfg, 0777); err != nil { - t.Fatalf("Failed to setup test cfg file: %v", err) - } - - testCmds := []string{ - "echo kexecloadresult ?(kexec -d -l -I /linux_image_cfg.json)", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/echo", - "github.com/u-root/u-root/cmds/core/kexec", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - fmt.Sprintf("%s:linux_image_cfg.json", cfgFile), - }, - }), - vmtest.WithQEMUFn( - qemu.WithVMTimeout(time.Minute), - qemu.ArbitraryArgs("-m", "8192"), - ), - // The initramfs will be placed in shared dir, so in the VM - // it's available at /testdata/initramfs.cpio. - vmtest.WithSharedDir(testtmp.TempDir(t)), - ) - - if _, err := vm.Console.ExpectString("kexecloadresult $ok"); err != nil { - t.Error(err) - } - if err := vm.Wait(); err != nil { - t.Errorf("Wait: %v", err) - } -} diff --git a/integration/generic-tests/multiboot_test.go b/integration/generic-tests/multiboot_test.go index 317fa1769b..384de750e1 100644 --- a/integration/generic-tests/multiboot_test.go +++ b/integration/generic-tests/multiboot_test.go @@ -10,6 +10,7 @@ package integration import ( "bytes" "encoding/json" + "io" "os" "path/filepath" "reflect" @@ -18,10 +19,18 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" + "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/boot/multiboot" - "github.com/u-root/u-root/pkg/uroot" ) +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { + return nil +} + func testMultiboot(t *testing.T, kernel string) { src := filepath.Join(os.Getenv("UROOT_MULTIBOOT_TEST_KERNEL_DIR"), kernel) if _, err := os.Stat(src); err != nil && os.IsNotExist(err) { @@ -30,24 +39,30 @@ func testMultiboot(t *testing.T, kernel string) { t.Error(err) } - dir := t.TempDir() - testCmds := []string{ - `kexec -l kernel -e -d --module="/kernel foo=bar" --module="/bbin/bb" | tee /testdata/output.json`, - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithSharedDir(dir), - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/kexec", - "github.com/u-root/u-root/cmds/core/tee", - ), - ExtraFiles: []string{ - src + ":kernel", - }, - }), + script := ` + kexec -l kernel -d --module="/kernel foo=bar" --module="/bbin/bb" + sync + kexec -e + ` + var b bytes.Buffer + vm := vmtest.StartVMAndRunCmds(t, script, + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + "github.com/u-root/u-root/cmds/core/sync", + ), + vmtest.WithInitramfsFiles( + src+":kernel", + ), vmtest.WithQEMUFn( + qemu.WithSerialOutput(nopCloser{&b}), qemu.WithVMTimeout(time.Minute), ), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString(`"status": "ok"`); err != nil { @@ -56,15 +71,12 @@ func testMultiboot(t *testing.T, kernel string) { if _, err := vm.Console.ExpectString(`}`); err != nil { t.Errorf(`expected '}' = end of JSON, got error: %v`, err) } - if err := vm.Wait(); err != nil { + if err := vm.Kill(); err != nil { t.Fatal(err) } + _ = vm.Wait() - output, err := os.ReadFile(filepath.Join(dir, "output.json")) - if err != nil { - t.Fatal(err) - } - t.Log(string(output)) + output := b.Bytes() i := bytes.Index(output, []byte(multiboot.DebugPrefix)) if i == -1 { diff --git a/integration/generic-tests/pxeboot_test.go b/integration/generic-tests/pxeboot_test.go index 88e5fedd76..871b6999b7 100644 --- a/integration/generic-tests/pxeboot_test.go +++ b/integration/generic-tests/pxeboot_test.go @@ -13,25 +13,22 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/hugelgupf/vmtest/qemu/network" + "github.com/hugelgupf/vmtest/qemu/qnetwork" "github.com/u-root/u-root/pkg/testutil" "github.com/u-root/u-root/pkg/uroot" ) // TestPxeboot runs a server and client to test pxebooting a node. func TestPxeboot4(t *testing.T) { - // TODO: support arm - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - - serverCmds := []string{ - "ip addr add 192.168.0.1/24 dev eth0", - "ip link set eth0 up", - "ip route add 0.0.0.0/0 dev eth0", - "ls -l /pxeroot", - "pxeserver -tftp-dir=/pxeroot", - } - net := network.NewInterVM() - serverVM := vmtest.StartVMAndRunCmds(t, serverCmds, + serverScript := ` + ip addr add 192.168.0.1/24 dev eth0 + ip link set eth0 up + ip route add 0.0.0.0/0 dev eth0 + ls -l /pxeroot + pxeserver -tftp-dir=/pxeroot + ` + net := qnetwork.NewInterVM() + serverVM := vmtest.StartVMAndRunCmds(t, serverScript, vmtest.WithName("TestPxeboot_Server"), vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( @@ -49,22 +46,10 @@ func TestPxeboot4(t *testing.T) { ), ) - testCmds := []string{ - "pxeboot --no-exec -v", - // Sleep so serial console output gets flushed. The expect library is racy. - "sleep 5", - "shutdown -h", - } - clientVM := vmtest.StartVMAndRunCmds(t, testCmds, + clientScript := "pxeboot --no-exec -v" + clientVM := vmtest.StartVMAndRunCmds(t, clientScript, vmtest.WithName("TestPxeboot_Client"), - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/ip", - "github.com/u-root/u-root/cmds/core/shutdown", - "github.com/u-root/u-root/cmds/core/sleep", - "github.com/u-root/u-root/cmds/boot/pxeboot", - ), - }), + vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/boot/pxeboot"), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), net.NewVM(), diff --git a/integration/generic-tests/tcz_test.go b/integration/generic-tests/tcz_test.go index 14fea7bae2..2a6968c771 100644 --- a/integration/generic-tests/tcz_test.go +++ b/integration/generic-tests/tcz_test.go @@ -14,7 +14,7 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/hugelgupf/vmtest/qemu/network" + "github.com/hugelgupf/vmtest/qemu/qnetwork" "github.com/u-root/u-root/pkg/uroot" ) @@ -24,18 +24,18 @@ func TestTczclient(t *testing.T) { t.Skip("This test is flaky, and must be fixed") - serverCmds := []string{ - "ip addr add 192.168.0.1/24 dev eth0", - "ip link set eth0 up", - "ip route add 255.255.255.255/32 dev eth0", - "ip l", - "ip a", - "srvfiles -h 192.168.0.1 -d /", - "echo The Server Completes", - "shutdown -h", - } - net := network.NewInterVM() - serverVM := vmtest.StartVMAndRunCmds(t, serverCmds, + serverScript := ` + ip addr add 192.168.0.1/24 dev eth0 + ip link set eth0 up + ip route add 255.255.255.255/32 dev eth0 + ip l + ip a + srvfiles -h 192.168.0.1 -d / + echo The Server Completes + shutdown -h + ` + net := qnetwork.NewInterVM() + serverVM := vmtest.StartVMAndRunCmds(t, serverScript, vmtest.WithName("TestTczclient_Server"), vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( @@ -55,20 +55,18 @@ func TestTczclient(t *testing.T) { ), ) - testCmds := []string{ - "ip addr add 192.168.0.2/24 dev eth0", - "ip link set eth0 up", - //"ip route add 255.255.255.255/32 dev eth0", - "ip a", - "tcz -d -h 192.168.0.1 -p 8080 libXcomposite libXdamage libXinerama libxshmfence", - "tcz -d -h 192.168.0.1 -p 8080 libXdmcp", - "echo HI THERE", - "ls /TinyCorePackages/tcloop", - "shutdown -h", - } + clientScript := ` + ip addr add 192.168.0.2/24 dev eth0 + ip link set eth0 up + ip a + tcz -d -h 192.168.0.1 -p 8080 libXcomposite libXdamage libXinerama libxshmfence + tcz -d -h 192.168.0.1 -p 8080 libXdmcp + echo HI THERE + ls /TinyCorePackages/tcloop + ` var b wc - clientVM := vmtest.StartVMAndRunCmds(t, testCmds, + clientVM := vmtest.StartVMAndRunCmds(t, clientScript, vmtest.WithName("TestTczclient_Client"), vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( diff --git a/integration/generic-tests/uefiboot_test.go b/integration/generic-tests/uefiboot_test.go index 9979e11bdb..657ff43c43 100644 --- a/integration/generic-tests/uefiboot_test.go +++ b/integration/generic-tests/uefiboot_test.go @@ -8,45 +8,58 @@ package integration import ( - "fmt" "os" - "path/filepath" "testing" "time" "github.com/Netflix/go-expect" "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" + "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/uroot" ) // TestUefiboot tests uefiboot commmands to boot to uefishell. -func TestUefiBoot(t *testing.T) { +func TestUEFIBoot(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) - payload := "UEFIPAYLOAD.fd" - src := fmt.Sprintf("/home/circleci/%v", payload) - if tk := os.Getenv("UROOT_TEST_UEFIPAYLOAD_DIR"); len(tk) > 0 { - src = filepath.Join(tk, payload) + var payload string + if tk := os.Getenv("UROOT_TEST_UEFIPAYLOAD"); len(tk) == 0 { + t.Skipf("UROOT_TEST_UEFIPAYLOAD not set to payload") + } else { + payload = tk } - if _, err := os.Stat(src); err != nil && os.IsNotExist(err) { - t.Skipf("UEFI payload image is not found: %s\n Usage: uefiboot ", src) + if _, err := os.Stat(payload); err != nil && os.IsNotExist(err) { + t.Skipf("UEFI payload image is not found: %s\n Usage: uefiboot ", payload) } - testCmds := []string{ - "uefiboot /dev/sda", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/exp/uefiboot", - )}), + // Separate loading and executing new kernel so GOCOVERDIR data is collected for uefiboot command. + script := ` + uefiboot -e=false /dev/sda + sync + kexec -e + ` + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/kexec", + "github.com/u-root/u-root/cmds/core/sync", + ), + // Since busybox mode rewrites commands, build uefiboot + // straight up as a binary to get integration test coverage. + vmtest.WithMergedInitramfs(uroot.Opts{ + Commands: uroot.BinaryCmds("github.com/u-root/u-root/cmds/exp/uefiboot"), + }), vmtest.WithQEMUFn( qemu.WithVMTimeout(2*time.Minute), - qemu.IDEBlockDevice(src), + qemu.IDEBlockDevice(payload), qemu.ArbitraryArgs("-machine", "q35"), qemu.ArbitraryArgs("-m", "4096"), ), + // Build uefiboot (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) // Edk2 debug mode will print PROGRESS CODE. We will want to make sure diff --git a/integration/gotests/gotest_test.go b/integration/gotests/gotest_test.go index ad1a2f74c5..abdc19b223 100644 --- a/integration/gotests/gotest_test.go +++ b/integration/gotests/gotest_test.go @@ -10,6 +10,7 @@ package integration import ( "os" "os/exec" + "slices" "strings" "testing" "time" @@ -34,7 +35,7 @@ func testPkgs(t *testing.T) []string { if err != nil { t.Fatal(err) } - pkgs := strings.Fields(strings.TrimSpace(string(out))) + allPkgs := strings.Fields(strings.TrimSpace(string(out))) // TODO: Some tests do not run properly in QEMU at the moment. They are // blocklisted. These tests fail for mostly two reasons: @@ -75,9 +76,9 @@ func testPkgs(t *testing.T) []string { "github.com/u-root/u-root/pkg/tss", "github.com/u-root/u-root/pkg/syscallfilter", } - if qemu.GuestArch() == qemu.ArchArm64 { - blocklist = append( - blocklist, + switch qemu.GuestArch() { + case qemu.ArchArm64: + blocklist = append(blocklist, "github.com/u-root/u-root/pkg/strace", // These tests run in 1-2 seconds on x86, but run @@ -87,29 +88,37 @@ func testPkgs(t *testing.T) []string { "github.com/u-root/u-root/cmds/exp/cbmem", "github.com/u-root/u-root/pkg/vfile", ) + + case qemu.ArchArm: + blocklist = append(blocklist, + "github.com/u-root/u-root/cmds/exp/cbmem", + + // These 4 tests do not compile on arm. + "github.com/u-root/u-root/pkg/boot/kexec", + "github.com/u-root/u-root/pkg/flash/chips", + "github.com/u-root/u-root/pkg/mount/gpt", + "github.com/u-root/u-root/pkg/mount/mtd", + ) } - for i := 0; i < len(pkgs); i++ { - for _, b := range blocklist { - if pkgs[i] == b { - pkgs = append(pkgs[:i], pkgs[i+1:]...) - } + + var pkgs []string + for _, p := range allPkgs { + if !slices.Contains(blocklist, p) { + pkgs = append(pkgs, p) } } - return pkgs } // TestGoTest effectively runs "go test ./..." inside a QEMU instance. The // tests run as root and can do all sorts of things not possible otherwise. func TestGoTest(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - pkgs := testPkgs(t) vmtest.RunGoTestsInVM(t, pkgs, vmtest.WithVMOpt( vmtest.WithMergedInitramfs(uroot.Opts{ - DefaultShell: "elvish", + DefaultShell: "gosh", Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/*", ), @@ -127,7 +136,8 @@ func TestGoTest(t *testing.T) { // e.g. pkg/mount/gpt/gpt_test.go need to claim 4.29G // // disk = make([]byte, 0x100000000) - qemu.ArbitraryArgs("-m", "6G"), + qemu.IfNotArch(qemu.ArchArm, qemu.ArbitraryArgs("-m", "6G")), + qemu.IfArch(qemu.ArchArm, qemu.ArbitraryArgs("-m", "3G")), // aarch64 VMs start at 1970-01-01 without RTC explicitly set. qemu.ArbitraryArgs("-rtc", "base=localtime,clock=vm"), diff --git a/pkg/acpi/acpi.go b/pkg/acpi/acpi.go index 6a63daa341..e0137f23eb 100644 --- a/pkg/acpi/acpi.go +++ b/pkg/acpi/acpi.go @@ -112,7 +112,7 @@ func String(t Table) string { func WriteTables(w io.Writer, tab Table, tabs ...Table) error { for _, tt := range append([]Table{tab}, tabs...) { if _, err := w.Write(tt.Data()); err != nil { - return fmt.Errorf("writing %s: %v", tt.Sig(), err) + return fmt.Errorf("writing %s: %w", tt.Sig(), err) } } return nil diff --git a/pkg/acpi/bios.go b/pkg/acpi/bios.go index cf104c57ce..644800b331 100644 --- a/pkg/acpi/bios.go +++ b/pkg/acpi/bios.go @@ -45,7 +45,7 @@ func ReadBiosTables() (*BiosTable, error) { Debug("Check Table at %#x", a) t, err := ReadRawTable(a) if err != nil { - return nil, fmt.Errorf("%#x:%v", a, err) + return nil, fmt.Errorf("%#x:%w", a, err) } Debug("Add table %s", String(t)) bios.Tables = append(bios.Tables, t) @@ -73,7 +73,7 @@ func ReadBiosTables() (*BiosTable, error) { // Fix that. t, err = ReadRawTable(int64(uint32(dsdt))) if err != nil { - return nil, fmt.Errorf("%#x:%v", uint64(dsdt), err) + return nil, fmt.Errorf("%#x:%w", uint64(dsdt), err) } Debug("Add table %s", String(t)) bios.Tables = append(bios.Tables, t) diff --git a/pkg/acpi/raw.go b/pkg/acpi/raw.go index 9929d0a668..887fb22c2e 100644 --- a/pkg/acpi/raw.go +++ b/pkg/acpi/raw.go @@ -31,10 +31,7 @@ var _ = Table(&Raw{}) // NewRaw returns a new Raw []Table fron a given byte slice. func NewRaw(b []byte) ([]Table, error) { var tab []Table - for { - if len(b) == 0 { - break - } + for len(b) != 0 { if len(b) < headerLength { return nil, fmt.Errorf("NewRaw: byte slice is only %d bytes and must be at least %d bytes", len(b), headerLength) } @@ -102,7 +99,7 @@ func (r *Raw) TableData() []byte { // Sig returns the table signature. func (r *Raw) Sig() string { - return fmt.Sprintf("%s", r.data[:4]) + return string(r.data[:4]) } // Len returns the total table length. diff --git a/pkg/align/align.go b/pkg/align/align.go index d401eb6dd1..3b31d63d1d 100644 --- a/pkg/align/align.go +++ b/pkg/align/align.go @@ -8,10 +8,12 @@ // size need be a power of 2. package align +import "golang.org/x/exp/constraints" + // Up aligns v up to next multiple of alignSize. // // alignSize need be a power of 2. -func Up(v uint, alignSize uint) uint { +func Up[T constraints.Unsigned](v T, alignSize T) T { mask := alignSize - 1 return (v + mask) &^ mask } @@ -19,16 +21,23 @@ func Up(v uint, alignSize uint) uint { // Down aligns v down to a previous multiple of alignSize. // // alignSize need be a power of 2. -func Down(v uint, alignSize uint) uint { +func Down[T constraints.Unsigned](v T, alignSize T) T { return Up(v-(alignSize-1), alignSize) } // UpPage aligns v up by system page size. -func UpPage(v uint) uint { - return Up(v, pageSize) +func UpPage[T constraints.Unsigned](v T) T { + return Up(v, T(pageSize)) } // DownPage aligns v down by system page size. -func DownPage(v uint) uint { - return Down(v, pageSize) +func DownPage[T constraints.Unsigned](v T) T { + return Down(v, T(pageSize)) +} + +// IsAligned checks whether v is aligned to alignSize. +// +// alignSize need be a power of 2. +func IsAligned[T constraints.Unsigned](v T, alignSize T) bool { + return v%alignSize == 0 } diff --git a/pkg/boot/boot.go b/pkg/boot/boot.go index 93d0ddf9ee..b86ab107aa 100644 --- a/pkg/boot/boot.go +++ b/pkg/boot/boot.go @@ -8,16 +8,11 @@ package boot import ( "fmt" - "os" - "path/filepath" "github.com/u-root/u-root/pkg/boot/kexec" "github.com/u-root/uio/ulog" ) -// DefaultLinuxImageCfgFile is the default file name in tmp directory to write loaded LinuxImage info to. -const DefaultLinuxImageCfgFile = "linux_image_cfg.json" - // LoadOption is an optional parameter to Load. type LoadOption func(*loadOptions) @@ -25,16 +20,13 @@ type loadOptions struct { logger ulog.Logger verbose bool callKexecLoad bool - // linuxImageCfgFile specifies where to writes loaded linuximage info to. - linuxImageCfgFile string } func defaultLoadOptions() *loadOptions { return &loadOptions{ - logger: ulog.Null, - verbose: false, - callKexecLoad: true, - linuxImageCfgFile: filepath.Join(os.TempDir(), DefaultLinuxImageCfgFile), + logger: ulog.Null, + verbose: false, + callKexecLoad: true, } } @@ -71,13 +63,6 @@ func WithDryRun(dryRun bool) LoadOption { } } -// WithLinuxImageCfgFile allows user to specify a different linux image config file path. -func WithLinuxImageCfgFile(f string) LoadOption { - return func(o *loadOptions) { - o.linuxImageCfgFile = f - } -} - // OSImage represents a bootable OS package. type OSImage interface { fmt.Stringer diff --git a/pkg/boot/boottest/json.go b/pkg/boot/boottest/json.go index 24b49991da..9957f16f77 100644 --- a/pkg/boot/boottest/json.go +++ b/pkg/boot/boottest/json.go @@ -46,11 +46,11 @@ func makeFileRel(u *url.URL) (*url.URL, error) { } relU, err := url.Parse(u.String()) if err != nil { - return nil, fmt.Errorf("error parsing %v: %v", u.String(), err) + return nil, fmt.Errorf("error parsing %v: %w", u.String(), err) } wd, err := os.Getwd() if err != nil { - return nil, fmt.Errorf("error from os.GetWd(): %v", err) + return nil, fmt.Errorf("error from os.GetWd(): %w", err) } relU.Path = strings.TrimPrefix(relU.Path, wd) return relU, nil @@ -81,7 +81,7 @@ func module(r io.ReaderAt) map[string]interface{} { func CompareImagesToJSON(imgs []boot.OSImage, jsonEncoded []byte) error { var want interface{} if err := json.Unmarshal(jsonEncoded, &want); err != nil { - return fmt.Errorf("failed to unmarshall test json %q: %v", jsonEncoded, err) + return fmt.Errorf("failed to unmarshall test json %q: %w", jsonEncoded, err) } got := ImagesToJSONLike(imgs) diff --git a/pkg/boot/boottest/same_image.go b/pkg/boot/boottest/same_image.go index 9611ff52e0..f72139f71b 100644 --- a/pkg/boot/boottest/same_image.go +++ b/pkg/boot/boottest/same_image.go @@ -9,7 +9,7 @@ import ( "io" "github.com/u-root/u-root/pkg/boot" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func mustReadAll(r io.ReaderAt) string { diff --git a/pkg/boot/bzimage/bzimage.go b/pkg/boot/bzimage/bzimage.go index d1eed75099..fb9e04104c 100644 --- a/pkg/boot/bzimage/bzimage.go +++ b/pkg/boot/bzimage/bzimage.go @@ -117,7 +117,7 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { stripped, err := stripSignature(d) if err != nil { - return fmt.Errorf("error stripping kernel signature: %v", err) + return fmt.Errorf("error stripping kernel signature: %w", err) } d = stripped @@ -161,11 +161,11 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { b.HeadCode = make([]byte, b.Header.PayloadOffset) if _, err := r.Read(b.HeadCode); err != nil { - return fmt.Errorf("can't read HeadCode: %v", err) + return fmt.Errorf("can't read HeadCode: %w", err) } b.compressed = make([]byte, b.Header.PayloadSize) if _, err := r.Read(b.compressed); err != nil { - return fmt.Errorf("can't read KernelCode: %v", err) + return fmt.Errorf("can't read KernelCode: %w", err) } decompressor, err := findDecompressor(b.compressed) if err != nil { @@ -190,14 +190,14 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { var uncompressedLength uint32 last4Bytes := b.compressed[(len(b.compressed) - 4):] if err := binary.Read(bytes.NewBuffer(last4Bytes), binary.LittleEndian, &uncompressedLength); err != nil { - return fmt.Errorf("error reading uncompressed kernel size: %v", err) + return fmt.Errorf("error reading uncompressed kernel size: %w", err) } Debug("Original length of uncompressed kernel is: %d", uncompressedLength) // Use the decompressor and write the decompressed payload into b.KernelCode. var buf bytes.Buffer if err := decompressor(&buf, bytes.NewBuffer(b.compressed)); err != nil { - return fmt.Errorf("error decompressing payload: %v", err) + return fmt.Errorf("error decompressing payload: %w", err) } b.KernelCode = buf.Bytes() @@ -217,13 +217,13 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { } if err := binary.Read(r, binary.LittleEndian, &b.CRC32); err != nil { - return fmt.Errorf("error reading CRC: %v", err) + return fmt.Errorf("error reading CRC: %w", err) } Debug("CRC read from image is: 0x%08x", b.CRC32) b.TailCode = make([]byte, r.Len()) // Read all remaining bytes. if _, err := r.Read(b.TailCode); err != nil { - return fmt.Errorf("can't read TailCode: %v", err) + return fmt.Errorf("can't read TailCode: %w", err) } // Generate the CRC checksum of the entire image until the end of sys_size. @@ -303,7 +303,7 @@ func stripSignature(image []byte) ([]byte, error) { return d, nil } if err := binary.Read(bytes.NewReader(d[peMagicOffset:]), binary.LittleEndian, peImage); err != nil { - return nil, fmt.Errorf("failed to read PE header: %v", err) + return nil, fmt.Errorf("failed to read PE header: %w", err) } // Verify that the image has the PE magic number. if !bytes.Equal(peImage.PEMagic[:], peMagic) { @@ -467,7 +467,7 @@ func compress(b []byte, dictOps string) ([]byte, error) { // "_with_size"). buf := bytes.NewBuffer(dat) if binary.Write(buf, binary.LittleEndian, uint32(len(b))); err != nil { - return nil, fmt.Errorf("failed to append the uncompressed size: %v", err) + return nil, fmt.Errorf("failed to append the uncompressed size: %w", err) } return buf.Bytes(), nil } @@ -695,7 +695,7 @@ func (b *BzImage) InitRAMFS() (int, int, error) { archiver, err := cpio.Format("newc") if err != nil { - return -1, -1, fmt.Errorf("format newc not supported: %v", err) + return -1, -1, fmt.Errorf("format newc not supported: %w", err) } var cur int for cur < len(dat) { diff --git a/pkg/boot/bzimage/bzimage_decompress.go b/pkg/boot/bzimage/bzimage_decompress.go index 5a8dd308c4..7ad64503e6 100644 --- a/pkg/boot/bzimage/bzimage_decompress.go +++ b/pkg/boot/bzimage/bzimage_decompress.go @@ -24,7 +24,7 @@ func stripSize(d decompressor) decompressor { // Read all of the bytes so that we can determine the size. allBytes, err := io.ReadAll(r) if err != nil { - return fmt.Errorf("error reading all bytes: %v", err) + return fmt.Errorf("error reading all bytes: %w", err) } strippedLen := int64(len(allBytes) - 4) Debug("Stripped reader is of length %d bytes", strippedLen) @@ -44,20 +44,20 @@ func execer(command string, args ...string) decompressor { stderrPipe, err := cmd.StderrPipe() if err != nil { - return fmt.Errorf("error creating Stderr pipe: %v", err) + return fmt.Errorf("error creating Stderr pipe: %w", err) } if err := cmd.Start(); err != nil { - return fmt.Errorf("error starting decompressor: %v", err) + return fmt.Errorf("error starting decompressor: %w", err) } stderr, err := io.ReadAll(stderrPipe) if err != nil { - return fmt.Errorf("error reading stderr: %v", err) + return fmt.Errorf("error reading stderr: %w", err) } if err := cmd.Wait(); err != nil || len(stderr) > 0 { - return fmt.Errorf("decompressor failed: err=%v, stderr=%q", err, stderr) + return fmt.Errorf("decompressor failed: err=%w, stderr=%q", err, stderr) } return nil } @@ -68,11 +68,11 @@ func execer(command string, args ...string) decompressor { func gunzip(w io.Writer, r io.Reader) error { gzipReader, err := gzip.NewReader(r) if err != nil { - return fmt.Errorf("error creating gzip reader: %v", err) + return fmt.Errorf("error creating gzip reader: %w", err) } if _, err := io.Copy(w, gzipReader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -82,11 +82,11 @@ func gunzip(w io.Writer, r io.Reader) error { func unlzma(w io.Writer, r io.Reader) error { lzmaReader, err := lzma.NewReader(r) if err != nil { - return fmt.Errorf("error creating lzma reader: %v", err) + return fmt.Errorf("error creating lzma reader: %w", err) } if _, err := io.Copy(w, lzmaReader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -97,7 +97,7 @@ func unlz4(w io.Writer, r io.Reader) error { lz4Reader := lz4.NewReader(r) if _, err := io.Copy(w, lz4Reader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -108,7 +108,7 @@ func unbzip2(w io.Writer, r io.Reader) error { bzip2Reader := bzip2.NewReader(r) if _, err := io.Copy(w, bzip2Reader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -118,12 +118,12 @@ func unbzip2(w io.Writer, r io.Reader) error { func unzstd(w io.Writer, r io.Reader) error { zstdReader, err := zstd.NewReader(r) if err != nil { - return fmt.Errorf("failed to create new reader: %v", err) + return fmt.Errorf("failed to create new reader: %w", err) } defer zstdReader.Close() if _, err := io.Copy(w, zstdReader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } diff --git a/pkg/boot/esxi/esxi.go b/pkg/boot/esxi/esxi.go index 083dadcab6..50494faa24 100644 --- a/pkg/boot/esxi/esxi.go +++ b/pkg/boot/esxi/esxi.go @@ -42,7 +42,7 @@ import ( "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/mount" "github.com/u-root/u-root/pkg/mount/gpt" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func partNo(device string, number int) (string, error) { diff --git a/pkg/boot/esxi/esxi_test.go b/pkg/boot/esxi/esxi_test.go index 6ad8abf70b..1de8d86629 100644 --- a/pkg/boot/esxi/esxi_test.go +++ b/pkg/boot/esxi/esxi_test.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/boot/multiboot" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func TestParse(t *testing.T) { diff --git a/pkg/boot/grub/grub.go b/pkg/boot/grub/grub.go index 5a9acde0c9..3b28a0be8d 100644 --- a/pkg/boot/grub/grub.go +++ b/pkg/boot/grub/grub.go @@ -34,8 +34,8 @@ import ( "github.com/u-root/u-root/pkg/mount" "github.com/u-root/u-root/pkg/mount/block" "github.com/u-root/u-root/pkg/shlex" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/ulog" + "github.com/u-root/uio/uio" ) var probeGrubFiles = []string{ diff --git a/pkg/boot/ibft/ibft.go b/pkg/boot/ibft/ibft.go index b465d8a0ad..fe9312a3d9 100644 --- a/pkg/boot/ibft/ibft.go +++ b/pkg/boot/ibft/ibft.go @@ -22,7 +22,7 @@ import ( "fmt" "net" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) var ( diff --git a/pkg/boot/initrd.go b/pkg/boot/initrd.go index 984beab264..ba7090eb9d 100644 --- a/pkg/boot/initrd.go +++ b/pkg/boot/initrd.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // CatInitrdsWithFileCache lazily reads up multiple initrds into single tmpfs file diff --git a/pkg/boot/initrd_test.go b/pkg/boot/initrd_test.go index 8d720b3c41..18fa7f6487 100644 --- a/pkg/boot/initrd_test.go +++ b/pkg/boot/initrd_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type file struct { diff --git a/pkg/boot/kexec/kexec_load_linux.go b/pkg/boot/kexec/kexec_load_linux.go index 0604ee4462..0e3c53f92a 100644 --- a/pkg/boot/kexec/kexec_load_linux.go +++ b/pkg/boot/kexec/kexec_load_linux.go @@ -6,6 +6,7 @@ package kexec import ( "fmt" + "runtime" "syscall" "unsafe" @@ -44,18 +45,60 @@ func (e ErrKexec) Error() string { return fmt.Sprintf("kexec_load(entry=%#x, segments=%s, flags %#x) = errno %s", e.Entry, e.Segments, e.Flags, e.Errno) } +// kexecSegment defines kernel memory layout. +type kexecSegment struct { + // Buf points to a buffer in user space. + Buf Range + + // Phys is a physical address of kernel. + Phys Range +} + +func (s Segment) toKexecSegment() kexecSegment { + if s.Buf == nil { + return kexecSegment{ + Buf: Range{Start: 0, Size: 0}, + Phys: s.Phys, + } + } + return kexecSegment{ + Buf: Range{ + Start: uintptr((unsafe.Pointer(&s.Buf[0]))), + Size: uint(len(s.Buf)), + }, + Phys: s.Phys, + } +} + +func (segs Segments) toKexecSegments() []kexecSegment { + var ks []kexecSegment + for _, seg := range segs { + ks = append(ks, seg.toKexecSegment()) + } + return ks +} + // rawLoad is a wrapper around kexec_load(2) syscall. // Preconditions: // - segments must not overlap // - segments must be full pages -func rawLoad(entry uintptr, segments []Segment, flags uint64) error { - if _, _, errno := unix.Syscall6( +func rawLoad(entry uintptr, segments Segments, flags uint64) error { + ks := segments.toKexecSegments() + _, _, errno := unix.Syscall6( unix.SYS_KEXEC_LOAD, entry, uintptr(len(segments)), - uintptr(unsafe.Pointer(&segments[0])), + uintptr(unsafe.Pointer(&ks[0])), uintptr(flags), - 0, 0); errno != 0 { + 0, 0) + // segments (and all the buffers therein) may have gotten freed after + // evaluating Syscall6 arguments, but before the syscall actually + // happens. + for _, seg := range segments { + runtime.KeepAlive(seg.Buf) + } + runtime.KeepAlive(segments) + if errno != 0 { return ErrKexec{ Entry: entry, Segments: segments, diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 25a2b57e26..c4c40e1ef1 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -9,31 +9,20 @@ import ( "debug/elf" "fmt" "io" - "log" "os" - "path" - "path/filepath" "reflect" "sort" - "strconv" "strings" "unsafe" "github.com/u-root/u-root/pkg/align" - "github.com/u-root/u-root/pkg/dt" ) var pageMask = uint(os.Getpagesize() - 1) // ErrNotEnoughSpace is returned by the FindSpace family of functions if no // range is large enough to accommodate the request. -type ErrNotEnoughSpace struct { - Size uint -} - -func (e ErrNotEnoughSpace) Error() string { - return fmt.Sprintf("not enough space to allocate %#x bytes", e.Size) -} +var ErrNotEnoughSpace = fmt.Errorf("not enough space to allocate bytes") // Range represents a contiguous uintptr interval [Start, Start+Size). type Range struct { @@ -54,6 +43,14 @@ func RangeFromInterval(start, end uintptr) Range { } } +// RangeFromInclusiveInterval returns a Range representing [start, last]. +func RangeFromInclusiveInterval(start, last uintptr) Range { + return Range{ + Start: start, + Size: uint(last - start + 1), + } +} + // String returns [Start, Start+Size) as a string. func (r Range) String() string { return fmt.Sprintf("[%#x, %#x)", r.Start, r.End()) @@ -64,6 +61,11 @@ func (r Range) End() uintptr { return r.Start + uintptr(r.Size) } +// Last returns last uintptr inside the interval. +func (r Range) Last() uintptr { + return r.Start + uintptr(r.Size) - 1 +} + // Adjacent returns true if r and r2 do not overlap, but are immediately next // to each other. func (r Range) Adjacent(r2 Range) bool { @@ -75,25 +77,16 @@ func (r Range) Contains(p uintptr) bool { return r.Start <= p && p < r.End() } -func min(a, b uintptr) uintptr { - if a < b { - return a - } - return b -} - -func minuint(a, b uint) uint { - if a < b { - return a - } - return b -} - -func max(a, b uintptr) uintptr { - if a > b { - return a +// WithStart returns a range that begins at start and ends at r.End(). +func (r Range) WithStart(start uintptr) Range { + switch { + case r.Start > start: + return Range{Start: start, Size: r.Size + uint(r.Start-start)} + case r.Start == start: + return Range{Start: start, Size: 0} + default: + return Range{Start: start, Size: r.Size - uint(start-r.Start)} } - return b } // Intersect returns the continuous range of points common to r and r2 if there @@ -169,14 +162,6 @@ func (rs Ranges) Minus(r Range) Ranges { return ram } -// FindSpace finds a continuous piece of sz points within Ranges and returns -// the Range pointing to it. -// -// If alignSizeBytes is zero, align up by page size. -func (rs Ranges) FindSpace(sz uint) (space Range, err error) { - return rs.FindSpaceAbove(sz, 0) -} - // MaxAddr is the highest address in a 64bit address space. const MaxAddr = ^uintptr(0) @@ -189,12 +174,73 @@ func (rs Ranges) FindSpaceAbove(sz uint, minAddr uintptr) (space Range, err erro // FindSpaceIn finds a continuous piece of sz points within Ranges and returns // a Range where space.Start >= limit.Start, with space.End() < limit.End(). func (rs Ranges) FindSpaceIn(sz uint, limit Range) (space Range, err error) { + return rs.FindSpace(sz, WithinRange(limit)) +} + +type findSpaceOptions struct { + limit Range + size uint + startAlign uint +} + +// FindOptioner is a config option for FindSpace. +type FindOptioner func(o *findSpaceOptions) + +// WithMinimumAddr requires FindSpace to return a range with an address above +// minAddr. +func WithMinimumAddr(minAddr uintptr) FindOptioner { + return func(o *findSpaceOptions) { + o.limit.Start = minAddr + o.limit.Size -= uint(minAddr) + } +} + +// WithinRange requires FindSpace to return a range within the limit. +func WithinRange(limit Range) FindOptioner { + return func(o *findSpaceOptions) { + o.limit = limit + } +} + +// WithAlignment requires FindSpace to return a range with an address and size +// aligned to alignSize. +func WithAlignment(alignSize uint) FindOptioner { + return func(o *findSpaceOptions) { + o.size = align.Up(o.size, alignSize) + o.startAlign = alignSize + } +} + +// WithStartAlignment requires FindSpace to return a range with an address +// aligned to alignSize. +func WithStartAlignment(alignSize uint) FindOptioner { + return func(o *findSpaceOptions) { + o.startAlign = alignSize + } +} + +// FindSpace finds a continuous piece of sz points within Ranges and the given +// options and returns the Range pointing to it. +func (rs Ranges) FindSpace(sz uint, opts ...FindOptioner) (Range, error) { + o := &findSpaceOptions{ + limit: RangeFromInterval(0, MaxAddr), + size: sz, + } + for _, opt := range opts { + opt(o) + } + if o.startAlign != 0 && !align.IsAligned(o.limit.Start, uintptr(o.startAlign)) { + o.limit = o.limit.WithStart(align.Up(o.limit.Start, uintptr(o.startAlign))) + } for _, r := range rs { - if overlap := r.Intersect(limit); overlap != nil && overlap.Size >= sz { - return Range{Start: overlap.Start, Size: sz}, nil + if o.startAlign != 0 && !align.IsAligned(r.Start, uintptr(o.startAlign)) { + r = r.WithStart(align.Up(r.Start, uintptr(o.startAlign))) + } + if overlap := r.Intersect(o.limit); overlap != nil && overlap.Size >= o.size { + return Range{Start: overlap.Start, Size: o.size}, nil } } - return Range{}, ErrNotEnoughSpace{Size: sz} + return Range{}, fmt.Errorf("%w: %#x bytes", ErrNotEnoughSpace, sz) } // Sort sorts ranges by their start point. @@ -209,14 +255,10 @@ func (rs Ranges) Sort() { }) } -// pool stores byte slices pointed by the pointers Segments.Buf to -// prevent underlying arrays to be collected by garbage collector. -var pool [][]byte - // Segment defines kernel memory layout. type Segment struct { - // Buf is a buffer in user space. - Buf Range + // Buf is a buffer to map to Phys in kexec. + Buf []byte // Phys is a physical address of kernel. Phys Range @@ -226,27 +268,20 @@ type Segment struct { // Segments should be created using NewSegment method to prevent // data pointed by Segment.Buf to be collected by garbage collector. func NewSegment(buf []byte, phys Range) Segment { - if buf == nil { - return Segment{ - Buf: Range{ - Start: 0, - Size: 0, - }, - Phys: phys, - } - } - pool = append(pool, buf) return Segment{ - Buf: Range{ - Start: uintptr((unsafe.Pointer(&buf[0]))), - Size: uint(len(buf)), - }, + Buf: buf, Phys: phys, } } +// SegmentEqual returns whether s and t point at the same physical region and +// contain the same data. +func SegmentEqual(s, t Segment) bool { + return s.Phys == t.Phys && bytes.Equal(s.Buf, t.Buf) +} + func (s Segment) String() string { - return fmt.Sprintf("(userspace: %s, phys: %s)", s.Buf, s.Phys) + return fmt.Sprintf("(phys: %s, buffer: size %#x)", s.Phys, len(s.Buf)) } // AlignAndMerge adjusts segs to the preconditions of kexec_load. @@ -315,9 +350,7 @@ func AlignAndMerge(segs Segments) (Segments, error) { // We don't need to deal with the inverse, because kexec_load // will fill the remainder of the segment with zeros anyway // when buf.Size < phys.Size. - if newSegs[i].Buf.Size > newSegs[i].Phys.Size { - newSegs[i].Buf.Size = newSegs[i].Phys.Size - } + newSegs[i].Buf = newSegs[i].realBufTruncate() newSegs[i].Phys.Size = align.UpPage(newSegs[i].Phys.Size) } return newSegs, nil @@ -327,14 +360,16 @@ func AlignAndMerge(segs Segments) (Segments, error) { // or be truncated. func (s Segment) realBufPad() []byte { switch { - case s.Buf.Size == s.Phys.Size: - return s.Buf.toSlice() + case uint(len(s.Buf)) == s.Phys.Size: + return s.Buf - case s.Buf.Size < s.Phys.Size: - return append(s.Buf.toSlice(), make([]byte, int(s.Phys.Size-s.Buf.Size))...) + case uint(len(s.Buf)) < s.Phys.Size: + // Pad Buf. + return append(s.Buf, make([]byte, int(s.Phys.Size-uint(len(s.Buf))))...) - case s.Buf.Size > s.Phys.Size: - return s.Buf.toSlice()[:s.Phys.Size] + case uint(len(s.Buf)) > s.Phys.Size: + // Truncate Buf. + return s.Buf[:s.Phys.Size] } return nil } @@ -342,17 +377,10 @@ func (s Segment) realBufPad() []byte { // realBufTruncate adjusts s.Buf.Size = s.Phys.Size, except when Buf is smaller // than Phys. Buf will either remain the same or be truncated. func (s Segment) realBufTruncate() []byte { - switch { - case s.Buf.Size == s.Phys.Size: - return s.Buf.toSlice() - - case s.Buf.Size < s.Phys.Size: - return s.Buf.toSlice() - - case s.Buf.Size > s.Phys.Size: - return s.Buf.toSlice()[:s.Phys.Size] + if uint(len(s.Buf)) > s.Phys.Size { + return s.Buf[:s.Phys.Size] } - return nil + return s.Buf } func (s *Segment) mergeDisjoint(s2 Segment) bool { @@ -378,6 +406,39 @@ func (s *Segment) mergeDisjoint(s2 Segment) bool { return true } +// Align aligns start and size by the given alignSize. +// +// The resulting range is guaranteed to be superset of r. +func (r Range) Align(alignSize uint) Range { + if alignSize == 0 { + return r + } + s := align.Down(r.Start, uintptr(alignSize)) + // Empty range remains empty. + if r.Size == 0 { + return Range{Start: s} + } + return Range{ + Start: s, + Size: align.Up(r.Size+uint(r.Start-s), alignSize), + } +} + +// AlignPage aligns start and size by page size. +// +// The resulting range is guaranteed to be superset of r. +func (r Range) AlignPage() Range { + s := align.DownPage(r.Start) + // Empty range remains empty. + if r.Size == 0 { + return Range{Start: s} + } + return Range{ + Start: s, + Size: align.UpPage(r.Size + uint(r.Start-s)), + } +} + // AlignPhysStart aligns s.Phys.Start to the page size. AlignPhysStart does not // align the size of the segment. func AlignPhysStart(s Segment) Segment { @@ -387,20 +448,22 @@ func AlignPhysStart(s Segment) Segment { diff := orig - s.Phys.Start s.Phys.Size = s.Phys.Size + uint(diff) - if s.Buf.Start < diff && diff > 0 { - panic("cannot have virtual memory address within first page") - } - s.Buf.Start -= diff - - if s.Buf.Size > 0 { - s.Buf.Size += uint(diff) - } + s.Buf = append(make([]byte, diff), s.Buf...) return s } // Segments is a collection of segments. type Segments []Segment +func (segs Segments) String() string { + var s strings.Builder + for _, seg := range segs { + s.WriteString(seg.String()) + s.WriteString("\n") + } + return s.String() +} + // PhysContains returns whether p exists in any of segs' physical memory // ranges. func (segs Segments) PhysContains(p uintptr) bool { @@ -421,11 +484,25 @@ func (segs Segments) Phys() Ranges { return r } +// SegmentsEqual returns whether the contents of all segments are the same, +// while pointing to the same physical memory region. +func SegmentsEqual(s, t Segments) bool { + if len(s) != len(t) { + return false + } + for i := range s { + if !SegmentEqual(s[i], t[i]) { + return false + } + } + return true +} + // IsSupersetOf checks whether all segments in o are present in s and contain // the same buffer content. func (segs Segments) IsSupersetOf(o Segments) error { for _, seg := range o { - size := minuint(seg.Phys.Size, seg.Buf.Size) + size := min(seg.Phys.Size, uint(len(seg.Buf))) if size == 0 { continue } @@ -434,7 +511,7 @@ func (segs Segments) IsSupersetOf(o Segments) error { if buf == nil { return fmt.Errorf("phys %s not found", r) } - if !bytes.Equal(buf, seg.Buf.toSlice()[:size]) { + if !bytes.Equal(buf, seg.Buf[:size]) { return fmt.Errorf("phys %s contains different bytes", r) } } @@ -447,7 +524,7 @@ func (segs Segments) GetPhys(r Range) []byte { if seg.Phys.IsSupersetOf(r) { offset := r.Start - seg.Phys.Start // TODO: This could be out of range. - buf := seg.Buf.toSlice()[int(offset) : int(offset)+int(r.Size)] + buf := seg.Buf[int(offset) : int(offset)+int(r.Size)] return buf } } @@ -516,179 +593,6 @@ func (m *Memory) LoadElfSegments(r io.ReaderAt) (Object, error) { return f, nil } -// ParseMemoryMap reads firmware provided memory map from /sys/firmware/memmap. -func (m *Memory) ParseMemoryMap() error { - p, err := ParseMemoryMap() - if err != nil { - return err - } - m.Phys = p - return nil -} - -// ParseMemoryMapFromFDT reads firmware provided memory map from an FDT. -func (m *Memory) ParseMemoryMapFromFDT(fdt *dt.FDT) error { - var phys MemoryMap - addMemory := func(n *dt.Node) error { - p, found := n.LookProperty("device_type") - if !found { - return nil - } - t, err := p.AsString() - if err != nil || t != "memory" { - return nil - } - p, found = n.LookProperty("reg") - if found { - r, err := p.AsRegion() - if err != nil { - return err - } - phys = append(phys, TypedRange{ - Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, - Type: RangeRAM, - }) - } - return nil - } - err := fdt.RootNode.Walk(addMemory) - if err != nil { - return err - } - - reserveMemory := func(n *dt.Node) error { - p, found := n.LookProperty("reg") - if found { - r, err := p.AsRegion() - if err != nil { - return err - } - - phys.Insert(TypedRange{ - Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, - Type: RangeReserved, - }) - } - return nil - } - resv, found := fdt.NodeByName("reserved-memory") - if found { - err := resv.Walk(reserveMemory) - if err != nil { - return err - } - } - - for _, r := range fdt.ReserveEntries { - phys.Insert(TypedRange{ - Range: Range{Start: uintptr(r.Address), Size: uint(r.Size)}, - Type: RangeReserved, - }) - } - - for _, r := range phys { - log.Printf("memmap: 0x%016x 0x%016x %s", r.Start, r.Size, r.Type) - } - m.Phys = phys - return nil -} - -var memoryMapRoot = "/sys/firmware/memmap/" - -// ParseMemoryMap reads firmware provided memory map from /sys/firmware/memmap. -func ParseMemoryMap() (MemoryMap, error) { - return internalParseMemoryMap(memoryMapRoot) -} - -func internalParseMemoryMap(memoryMapDir string) (MemoryMap, error) { - type memRange struct { - // start and end addresses are inclusive - start, end uintptr - typ RangeType - } - - ranges := make(map[string]memRange) - walker := func(name string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - - const ( - // file names - start = "start" - end = "end" - typ = "type" - ) - - base := path.Base(name) - if base != start && base != end && base != typ { - return fmt.Errorf("unexpected file %q", name) - } - dir := path.Dir(name) - - b, err := os.ReadFile(name) - if err != nil { - return fmt.Errorf("error reading file %q: %v", name, err) - } - - data := strings.TrimSpace(string(b)) - r := ranges[dir] - if base == typ { - typ, ok := sysfsToRangeType[data] - if !ok { - log.Printf("Sysfs file %q contains unrecognized memory map type %q, defaulting to Reserved", name, data) - r.typ = RangeReserved - } else { - r.typ = typ - } - ranges[dir] = r - return nil - } - - v, err := strconv.ParseUint(data, 0, strconv.IntSize) - if err != nil { - return err - } - switch base { - case start: - r.start = uintptr(v) - case end: - r.end = uintptr(v) - } - ranges[dir] = r - return nil - } - - if err := filepath.Walk(memoryMapDir, walker); err != nil { - return nil, err - } - - var phys []TypedRange - for _, r := range ranges { - // Range's end address is exclusive, while Linux's sysfs prints - // the end address inclusive. - // - // E.g. sysfs will contain - // - // start: 0x100, end: 0x1ff - // - // while we represent - // - // start: 0x100, size: 0x100. - phys = append(phys, TypedRange{ - Range: RangeFromInterval(r.start, r.end+1), - Type: r.typ, - }) - } - sort.Slice(phys, func(i, j int) bool { - return phys[i].Start < phys[j].Start - }) - return phys, nil -} - // M1 is 1 Megabyte in bits. const M1 = 1 << 20 @@ -747,13 +651,7 @@ func (m *Memory) AddKexecSegment(d []byte) (Range, error) { // AddKexecSegmentExplicit adds d to a new kexec segment, but allows asking // for extra space, secifying alignment size, and setting text_offset. func (m *Memory) AddKexecSegmentExplicit(d []byte, sz, offset, alignSizeBytes uint) (Range, error) { - if sz < uint(len(d)) { - return Range{}, fmt.Errorf("length of d is more than size requested") - } - if offset > sz { - return Range{}, fmt.Errorf("offset is larger than size requested") - } - r, err := m.FindSpace(sz, alignSizeBytes) + r, err := m.AvailableRAM().FindSpace(offset+sz, WithAlignment(alignSizeBytes)) if err != nil { return Range{}, err } @@ -779,9 +677,9 @@ func (m *Memory) AddKexecSegmentExplicit(d []byte, sz, offset, alignSizeBytes ui // // [{start:0 size:40} {start:4096 end:8000 - 4096}] func (m Memory) AvailableRAM() Ranges { - ram := m.Phys.FilterByType(RangeRAM) + ram := m.Phys.RAM() - // Remove all points in Segments from available RAM. + // Remove all points we've already reserved from available RAM. for _, s := range m.Segments { ram = ram.Minus(s.Phys) } @@ -799,135 +697,3 @@ func (m Memory) AvailableRAM() Ranges { } return alignedRanges } - -// RangeType defines type of a TypedRange based on the Linux -// kernel string provided by firmware memory map. -type RangeType string - -// These are the range types we know Linux uses. -const ( - RangeRAM RangeType = "System RAM" - RangeDefault RangeType = "Default" - RangeACPI RangeType = "ACPI Tables" - RangeNVS RangeType = "ACPI Non-volatile Storage" - RangeReserved RangeType = "Reserved" -) - -// String implements fmt.Stringer. -func (r RangeType) String() string { - return string(r) -} - -var sysfsToRangeType = map[string]RangeType{ - "System RAM": RangeRAM, - "Default": RangeDefault, - "ACPI Tables": RangeACPI, - "ACPI Non-volatile Storage": RangeNVS, - "Reserved": RangeReserved, - "reserved": RangeReserved, -} - -// TypedRange represents range of physical memory. -type TypedRange struct { - Range - Type RangeType -} - -func (tr TypedRange) String() string { - return fmt.Sprintf("{addr: %s, type: %s}", tr.Range, tr.Type) -} - -// MemoryMap defines the layout of physical memory. -// -// MemoryMap defines which ranges in memory are usable RAM and which are -// reserved for various reasons. -type MemoryMap []TypedRange - -// FilterByType only returns ranges of the given typ. -func (m MemoryMap) FilterByType(typ RangeType) Ranges { - var rs Ranges - for _, tr := range m { - if tr.Type == typ { - rs = append(rs, tr.Range) - } - } - return rs -} - -func (m MemoryMap) sort() { - sort.Slice(m, func(i, j int) bool { - return m[i].Start < m[j].Start - }) -} - -// Insert a new TypedRange into the memory map, removing chunks of other ranges -// as necessary. -// -// Assumes that TypedRange is a valid range -- no checking. -func (m *MemoryMap) Insert(r TypedRange) { - var newMap MemoryMap - - // Remove points in r from all existing physical ranges. - for _, q := range *m { - split := q.Range.Minus(r.Range) - for _, r2 := range split { - newMap = append(newMap, TypedRange{Range: r2, Type: q.Type}) - } - } - - newMap = append(newMap, r) - newMap.sort() - *m = newMap -} - -// PayloadMemType defines type of a memory map entry -type PayloadMemType uint32 - -// Payload memory type (PayloadMemType) in UefiPayload -const ( - PayloadTypeRAM = 1 - PayloadTypeDefault = 2 - PayloadTypeACPI = 3 - PayloadTypeNVS = 4 - PayloadTypeReserved = 5 -) - -// payloadMemoryMapEntry represent a memory map entry in payload param -type payloadMemoryMapEntry struct { - Start uint64 - End uint64 - Type PayloadMemType -} - -// PayloadMemoryMapParam is payload's MemoryMap parameter -type PayloadMemoryMapParam []payloadMemoryMapEntry - -var rangeTypeToPayloadMemType = map[RangeType]PayloadMemType{ - RangeRAM: PayloadTypeRAM, - RangeDefault: PayloadTypeDefault, - RangeACPI: PayloadTypeACPI, - RangeNVS: PayloadTypeNVS, - RangeReserved: PayloadTypeReserved, -} - -func convertToPayloadMemType(rt RangeType) PayloadMemType { - mt, ok := rangeTypeToPayloadMemType[rt] - if !ok { - // return reserved if range type is not recognized - return PayloadTypeReserved - } - return mt -} - -// AsPayloadParam converts MemoryMap to a PayloadMemoryMapParam -func (m *MemoryMap) AsPayloadParam() PayloadMemoryMapParam { - var p PayloadMemoryMapParam - for _, entry := range *m { - p = append(p, payloadMemoryMapEntry{ - Start: uint64(entry.Start), - End: uint64(entry.Start) + uint64(entry.Size) - 1, - Type: convertToPayloadMemType(entry.Type), - }) - } - return p -} diff --git a/pkg/boot/kexec/memory_linux_test.go b/pkg/boot/kexec/memory_linux_test.go index ce3ed57502..4248a7a6c7 100644 --- a/pkg/boot/kexec/memory_linux_test.go +++ b/pkg/boot/kexec/memory_linux_test.go @@ -5,278 +5,14 @@ package kexec import ( + "errors" "fmt" - "os" - "path" "reflect" "testing" "github.com/google/go-cmp/cmp" - "github.com/u-root/u-root/pkg/dt" ) -func checkMemoryMap(t *testing.T, got, want MemoryMap) { - t.Helper() - if len(got) != len(want) { - t.Errorf("got memory map length %d, want memory map length %d", len(got), len(want)) - } - for idx, r := range got { - if r.Type != want[idx].Type { - t.Errorf("got memory at index %d type %v, want type %v", idx, r.Type, want[idx].Type) - } - if r.Range.Start != want[idx].Start || r.Size != want[idx].Size { - t.Errorf("got memory at index %d range %v, want range %v", idx, r.Range, want[idx].Range) - } - } -} - -func TestParseMemoryMapFromFDT(t *testing.T) { - for _, tc := range []struct { - name string - fdt *dt.FDT - wantMap MemoryMap - wantErr error - }{ - { - "empty", - &dt.FDT{RootNode: &dt.Node{Name: "/"}}, - MemoryMap{}, - nil, - }, - { - "add system memory ok", - &dt.FDT{ - RootNode: &dt.Node{ - Name: "/", - Children: []*dt.Node{ - { - Name: "test memory", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 2", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 3", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - }, - }, - }, - MemoryMap{ - TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffffffff}, "System RAM"}, - TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, - TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0x2ffffffffffff}, "System RAM"}, - }, - nil, - }, - { - "add system memory, and reserved memory ok", - &dt.FDT{ - RootNode: &dt.Node{ - Name: "/", - Children: []*dt.Node{ - { - Name: "test memory", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 2", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 3", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "reserved-memory", - Properties: []dt.Property{ - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, - }, - Children: []*dt.Node{ - { - Name: "reserved mem child node", - Properties: []dt.Property{ - { - "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, - }, - }, - }, - }, - }, - }, - }, - }, - MemoryMap{ - TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". - TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, - TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". - }, - nil, - }, - { - "add system memory, reserved memory, and reserved entries ok", - &dt.FDT{ - ReserveEntries: []dt.ReserveEntry{ - { - Address: uint64(0x1000000000000), - Size: uint64(0xffff), - }, - }, - RootNode: &dt.Node{ - Name: "/", - Children: []*dt.Node{ - { - Name: "test memory", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 2", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 3", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "reserved-memory", - Properties: []dt.Property{ - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, - }, - Children: []*dt.Node{ - { - Name: "reserved mem child node", - Properties: []dt.Property{ - { - "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, - }, - }, - }, - }, - }, - }, - }, - }, - MemoryMap{ - TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". - TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0xffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0x100000000ffff), Size: 0x1ffffffff0000}, "System RAM"}, // carve out reserve entry. - TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". - }, - nil, - }, - } { - t.Run(tc.name, func(t *testing.T) { - km := &Memory{} - err := km.ParseMemoryMapFromFDT(tc.fdt) - if err != tc.wantErr { - t.Errorf("km.ParseMemoryMapFromFDT returned error %v, want error %v", err, tc.wantErr) - } - checkMemoryMap(t, km.Phys, tc.wantMap) - }) - } -} - -func TestParseMemoryMap(t *testing.T) { - root := t.TempDir() - - create := func(dir string, start, end uintptr, typ RangeType) error { - p := path.Join(root, dir) - if err := os.Mkdir(p, 0o755); err != nil { - return err - } - if err := os.WriteFile(path.Join(p, "start"), []byte(fmt.Sprintf("%#x\n", start)), 0o655); err != nil { - return err - } - if err := os.WriteFile(path.Join(p, "end"), []byte(fmt.Sprintf("%#x\n", end)), 0o655); err != nil { - return err - } - return os.WriteFile(path.Join(p, "type"), append([]byte(typ), '\n'), 0o655) - } - - if err := create("0", 0, 49, RangeRAM); err != nil { - t.Fatal(err) - } - if err := create("1", 100, 149, RangeACPI); err != nil { - t.Fatal(err) - } - if err := create("2", 200, 249, RangeNVS); err != nil { - t.Fatal(err) - } - if err := create("3", 300, 349, RangeReserved); err != nil { - t.Fatal(err) - } - - want := MemoryMap{ - {Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, - {Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, - {Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, - {Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, - } - - phys, err := internalParseMemoryMap(root) - if err != nil { - t.Fatalf("ParseMemoryMap() error: %v", err) - } - if !reflect.DeepEqual(phys, want) { - t.Errorf("ParseMemoryMap() got %v, want %v", phys, want) - } -} - -func TestAsPayloadParam(t *testing.T) { - var mem Memory - mem.Phys = MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, - TypedRange{Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, - TypedRange{Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, - TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeRAM}, - } - want := PayloadMemoryMapParam{ - {Start: 0, End: 49, Type: PayloadTypeRAM}, - {Start: 100, End: 149, Type: PayloadTypeACPI}, - {Start: 200, End: 249, Type: PayloadTypeNVS}, - {Start: 300, End: 349, Type: PayloadTypeReserved}, - {Start: 400, End: 449, Type: PayloadTypeRAM}, - } - mm := mem.Phys.AsPayloadParam() - if !reflect.DeepEqual(mm, want) { - t.Errorf("MemoryMap.AsPayloadParam() got %v, want %v", mm, want) - } -} - func TestAvailableRAM(t *testing.T) { old := pageMask defer func() { @@ -344,7 +80,7 @@ func TestAlignAndMerge(t *testing.T) { NewSegment(nil, Range{Start: 0, Size: 0x1000}), }, want: Segments{ - NewSegment(nil, Range{Start: 0, Size: 0x1000}), + NewSegment([]byte{}, Range{Start: 0, Size: 0x1000}), }, }, { @@ -446,8 +182,7 @@ func TestAlignAndMerge(t *testing.T) { t.Errorf("AlignAndMerge physical ranges = (-want, +got):\n%s", diff) } for i, s := range got { - b := s.Buf.toSlice() - if diff := cmp.Diff(tt.want[i].Buf.toSlice(), b); diff != "" { + if diff := cmp.Diff(tt.want[i].Buf, s.Buf); diff != "" { t.Errorf("segment %s bytes differ (-want, +got):\n%s", got[i].Phys, diff) } } @@ -471,7 +206,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0x1000, MaxAddr), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no space under 0x1000", @@ -480,7 +215,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0, 0x1000), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "disjunct space above 0x1000", @@ -514,7 +249,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0x1000, 0x2000), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "space is split across 0x1000, with enough space above", @@ -542,7 +277,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0x1000, 0x2000), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "space is split across 0x1000, with enough space in the next one", @@ -559,20 +294,23 @@ func TestFindSpaceIn(t *testing.T) { rs: Ranges{}, size: 0x10, limit: RangeFromInterval(0, MaxAddr), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no ranges, zero size", rs: Ranges{}, size: 0, limit: RangeFromInterval(0, MaxAddr), - err: ErrNotEnoughSpace{Size: 0}, + err: ErrNotEnoughSpace, }, } { t.Run(fmt.Sprintf("test_%d_%s", i, tt.name), func(t *testing.T) { got, err := tt.rs.FindSpaceIn(tt.size, tt.limit) - if !reflect.DeepEqual(got, tt.want) || err != tt.err { - t.Errorf("%s.FindSpaceIn(%#x, limit = %s) = (%#x, %v), want (%#x, %v)", tt.rs, tt.size, tt.limit, got, err, tt.want, tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%s.FindSpaceIn(%#x, limit = %s) = %#x, want %#x", tt.rs, tt.size, tt.limit, got, tt.want) + } + if !errors.Is(err, tt.err) { + t.Errorf("%s.FindSpaceIn(%#x, limit = %s) = %v, want %v", tt.rs, tt.size, tt.limit, err, tt.err) } }) } @@ -582,6 +320,7 @@ func TestFindSpace(t *testing.T) { for i, tt := range []struct { name string rs Ranges + opts []FindOptioner size uint want Range err error @@ -600,19 +339,184 @@ func TestFindSpace(t *testing.T) { name: "no ranges", rs: Ranges{}, size: 0x10, - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no ranges, zero size", rs: Ranges{}, size: 0, - err: ErrNotEnoughSpace{Size: 0}, + err: ErrNotEnoughSpace, + }, + { + name: "no space above 0x1000", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + err: ErrNotEnoughSpace, + }, + { + name: "disjunct space above 0x1000", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "space is split across 0x1000, with enough space above", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1010}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "space is split across 0x1000, with enough space in the next one", + rs: Ranges{ + Range{Start: 0x0, Size: 0x100f}, + Range{Start: 0x1010, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + want: Range{Start: 0x1010, Size: 0x10}, + }, + { + name: "just enough space under 0x1000", + rs: Ranges{ + Range{Start: 0xFF, Size: 0xf}, + Range{Start: 0xFF0, Size: 0x10}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + want: Range{Start: 0xFF0, Size: 0x10}, + }, + { + name: "no space under 0x1000", + rs: Ranges{ + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0, 0x1000))}, + err: ErrNotEnoughSpace, + }, + { + name: "disjunct space above 0x1000", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, MaxAddr))}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "just enough space under 0x1000", + rs: Ranges{ + Range{Start: 0xFF, Size: 0xf}, + Range{Start: 0xFF0, Size: 0x10}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0, 0x1000))}, + want: Range{Start: 0xFF0, Size: 0x10}, + }, + { + name: "all spaces abvoe 0x1000 and under 0x2000 are too small", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1000, Size: 0xf}, + Range{Start: 0x1010, Size: 0xf}, + Range{Start: 0x1f00, Size: 0xf}, + Range{Start: 0x2000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, 0x2000))}, + err: ErrNotEnoughSpace, + }, + { + name: "space is split across 0x1000, with enough space above", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1010}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, MaxAddr))}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "space is split across 0x1000, with enough space under", + rs: Ranges{ + Range{Start: 0xFF0, Size: 0x20}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0, 0x1000))}, + want: Range{Start: 0xFF0, Size: 0x10}, + }, + { + name: "space is split across 0x1000 and 0x2000, but not enough space above or below", + rs: Ranges{ + Range{Start: 0xFF1, Size: 0xf + 0xf}, + Range{Start: 0x1FF1, Size: 0xf + 0xf}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, 0x2000))}, + err: ErrNotEnoughSpace, + }, + { + name: "space is split across 0x1000, with enough space in the next one", + rs: Ranges{ + Range{Start: 0x0, Size: 0x100f}, + Range{Start: 0x1010, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, MaxAddr))}, + want: Range{Start: 0x1010, Size: 0x10}, + }, + { + name: "alignment with limit", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1010, Size: 0x10}, + Range{Start: 0x2000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x500, MaxAddr)), WithStartAlignment(0x1000)}, + want: Range{Start: 0x2000, Size: 0x10}, + }, + { + name: "alignment with limit", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1010, Size: 0x1010}, + Range{Start: 0x3000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x500, MaxAddr)), WithStartAlignment(0x1000)}, + want: Range{Start: 0x2000, Size: 0x10}, + }, + { + name: "alignment with limit", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1010, Size: 0x1010}, + Range{Start: 0x3000, Size: 0x1000}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x500, MaxAddr)), WithAlignment(0x1000)}, + want: Range{Start: 0x3000, Size: 0x1000}, }, } { t.Run(fmt.Sprintf("test_%d_%s", i, tt.name), func(t *testing.T) { - got, err := tt.rs.FindSpace(tt.size) - if !reflect.DeepEqual(got, tt.want) || err != tt.err { - t.Errorf("%s.FindSpace(%#x) = (%#x, %v), want (%#x, %v)", tt.rs, tt.size, got, err, tt.want, tt.err) + got, err := tt.rs.FindSpace(tt.size, tt.opts...) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%s.FindSpace(%#x) = %#x, want %#x", tt.rs, tt.size, got, tt.want) + } + if !errors.Is(err, tt.err) { + t.Errorf("%s.FindSpace(%#x) = %v, want %v", tt.rs, tt.size, err, tt.err) } }) } @@ -634,7 +538,7 @@ func TestFindSpaceAbove(t *testing.T) { }, size: 0x10, min: 0x1000, - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "disjunct space above 0x1000", @@ -680,19 +584,22 @@ func TestFindSpaceAbove(t *testing.T) { name: "no ranges", rs: Ranges{}, size: 0x10, - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no ranges, zero size", rs: Ranges{}, size: 0, - err: ErrNotEnoughSpace{Size: 0}, + err: ErrNotEnoughSpace, }, } { t.Run(fmt.Sprintf("test_%d_%s", i, tt.name), func(t *testing.T) { got, err := tt.rs.FindSpaceAbove(tt.size, tt.min) - if !reflect.DeepEqual(got, tt.want) || err != tt.err { - t.Errorf("%s.FindSpaceAbove(%#x, min=%#x) = (%#x, %v), want (%#x, %v)", tt.rs, tt.size, tt.min, got, err, tt.want, tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%s.FindSpaceAbove(%#x, min=%#x) = %#x, want %#x", tt.rs, tt.size, tt.min, got, tt.want) + } + if !errors.Is(err, tt.err) { + t.Errorf("%s.FindSpaceAbove(%#x, min=%#x) = %v, want %v", tt.rs, tt.size, tt.min, err, tt.err) } }) } @@ -922,69 +829,6 @@ func TestAdjacent(t *testing.T) { } } -func TestMemoryMapInsert(t *testing.T) { - for i, tt := range []struct { - m MemoryMap - r TypedRange - want MemoryMap - }{ - { - // r is entirely within m's one range. - m: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x2000}, Type: RangeRAM}, - }, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - TypedRange{Range: Range{Start: 0x200, Size: 0x2000 - 0x200}, Type: RangeRAM}, - }, - }, - { - // r sits across three RAM ranges. - m: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x150}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x150, Size: 0x50}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x1a0, Size: 0x100}, Type: RangeRAM}, - }, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - TypedRange{Range: Range{Start: 0x200, Size: 0xa0}, Type: RangeRAM}, - }, - }, - { - // r is a superset of the ranges in m. - m: MemoryMap{ - TypedRange{Range: Range{Start: 0x100, Size: 0x50}, Type: RangeRAM}, - }, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - }, - }, - { - // r is the first range in the map. - m: MemoryMap{}, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - }, - }, - } { - t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { - // Make a copy for the Errorf print. - m := tt.m - tt.m.Insert(tt.r) - - if !reflect.DeepEqual(tt.m, tt.want) { - t.Errorf("\n%v.Insert(%s) =\n%v, want\n%v", m, tt.r, tt.m, tt.want) - } - }) - } -} - func TestSegmentsInsert(t *testing.T) { for i, tt := range []struct { segs Segments @@ -1063,3 +907,115 @@ func TestIsSupersetOf(t *testing.T) { } } } + +func TestRanges(t *testing.T) { + for _, tt := range []struct { + start uintptr + end uintptr + conv func(uintptr, uintptr) Range + want Range + }{ + { + start: 0, + end: 0x1000, + conv: RangeFromInterval, + want: Range{Start: 0, Size: 0x1000}, + }, + { + start: 0, + end: 0xfff, + conv: RangeFromInclusiveInterval, + want: Range{Start: 0, Size: 0x1000}, + }, + { + start: 0, + end: 0, + conv: RangeFromInterval, + want: Range{Start: 0, Size: 0}, + }, + { + start: 0, + end: 0, + conv: RangeFromInclusiveInterval, + want: Range{Start: 0, Size: 0x1}, + }, + } { + got := tt.conv(tt.start, tt.end) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Range(%#x, %#x) = %v, want %v", tt.start, tt.end, got, tt.want) + } + } +} + +func TestAlign(t *testing.T) { + for _, tt := range []struct { + r Range + alignSize uint + want Range + }{ + { + r: Range{Start: 0x10, Size: 0x10}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0x1000}, + }, + { + r: Range{Start: 0x10, Size: 0}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0x10}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0x1000}, + }, + { + r: Range{Start: 0x10, Size: 0x10}, + alignSize: 0, + want: Range{Start: 0x10, Size: 0x10}, + }, + { + r: Range{Start: 0x10, Size: 0x10}, + alignSize: 1, + want: Range{Start: 0x10, Size: 0x10}, + }, + } { + got := tt.r.Align(tt.alignSize) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%v.Align(%#x) = %v, want %v", tt.r, tt.alignSize, got, tt.want) + } + } +} + +func TestAlignPage(t *testing.T) { + for _, tt := range []struct { + r Range + want Range + }{ + { + r: Range{Start: 0x10, Size: 0x10}, + want: Range{Start: 0, Size: 0x1000}, + }, + { + r: Range{Start: 0x10, Size: 0}, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0}, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0x10}, + want: Range{Start: 0, Size: 0x1000}, + }, + } { + got := tt.r.AlignPage() + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%v.AlignPage() = %v, want %v", tt.r, got, tt.want) + } + } +} diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go new file mode 100644 index 0000000000..34656c0c60 --- /dev/null +++ b/pkg/boot/kexec/memory_map_linux.go @@ -0,0 +1,510 @@ +// Copyright 2015-2019 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kexec + +import ( + "bufio" + "fmt" + "io" + "log" + "os" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/u-root/u-root/pkg/dt" +) + +// RangeType defines type of a TypedRange based on the Linux +// kernel string provided by firmware memory map. +type RangeType string + +// These are the range types we know Linux uses. +const ( + RangeRAM RangeType = "System RAM" + RangeDefault RangeType = "Default" + RangeACPI RangeType = "ACPI Tables" + RangeNVS RangeType = "ACPI Non-volatile Storage" + RangeReserved RangeType = "Reserved" +) + +// String implements fmt.Stringer. +func (r RangeType) String() string { + return string(r) +} + +var sysfsToRangeType = map[string]RangeType{ + "System RAM": RangeRAM, + "Default": RangeDefault, + "ACPI Tables": RangeACPI, + "ACPI Non-volatile Storage": RangeNVS, + "Reserved": RangeReserved, + "reserved": RangeReserved, +} + +// TypedRange represents range of physical memory. +type TypedRange struct { + Range + Type RangeType +} + +func (tr TypedRange) String() string { + return fmt.Sprintf("{addr: %s, type: %s}", tr.Range, tr.Type) +} + +// MemoryMap defines the layout of physical memory. +// +// MemoryMap defines which ranges in memory are usable RAM and which are +// reserved for various reasons. +type MemoryMap []TypedRange + +func (mm MemoryMap) String() string { + var s strings.Builder + for _, tr := range mm { + s.WriteString(tr.String()) + s.WriteString("\n") + } + return s.String() +} + +// FilterByType only returns ranges of the given typ. +func (mm MemoryMap) FilterByType(typ RangeType) Ranges { + var rs Ranges + for _, tr := range mm { + if tr.Type == typ { + rs = append(rs, tr.Range) + } + } + return rs +} + +// RAM is an alias for FilterByType(RangeRAM) and returns unreserved physical +// memory in the memory map. +func (mm MemoryMap) RAM() Ranges { + return mm.FilterByType(RangeRAM) +} + +func (mm MemoryMap) sort() { + sort.Slice(mm, func(i, j int) bool { + return mm[i].Start < mm[j].Start + }) +} + +func (mm *MemoryMap) mergeAdjacent() { + if len(*mm) == 0 { + return + } + + newMap := MemoryMap{(*mm)[0]} + for i := 1; i < len(*mm); i++ { + seg := (*mm)[i] + + prev := newMap[len(newMap)-1] + mergable := seg.Range.Overlaps(prev.Range) || seg.Range.Adjacent(prev.Range) + // Does the range overlap with the previous range? Merge them. + if mergable && seg.Type == prev.Type { + // Assuming the map is sorted by start, as it always + // should be, extend the size. + if seg.End() > prev.End() { + diffSize := seg.End() - prev.End() + newMap[len(newMap)-1].Range.Size += uint(diffSize) + } + } else { + newMap = append(newMap, seg) + } + } + *mm = newMap +} + +// Insert a new TypedRange into the memory map, removing chunks of other ranges +// as necessary. +// +// Assumes that TypedRange is a valid range -- no checking. +func (mm *MemoryMap) Insert(r TypedRange) { + var newMap MemoryMap + + // Remove points in r from all existing physical ranges. + for _, q := range *mm { + split := q.Range.Minus(r.Range) + for _, r2 := range split { + newMap = append(newMap, TypedRange{Range: r2, Type: q.Type}) + } + } + + newMap = append(newMap, r) + newMap.sort() + *mm = newMap +} + +// MemoryMapFromFDT reads firmware provided memory map from an FDT. +func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { + var mm MemoryMap + addMemory := func(n *dt.Node) error { + p, found := n.LookProperty("device_type") + if !found { + return nil + } + t, err := p.AsString() + if err != nil || t != "memory" { + return nil + } + p, found = n.LookProperty("reg") + if found { + r, err := p.AsRegion() + if err != nil { + return err + } + mm = append(mm, TypedRange{ + Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, + Type: RangeRAM, + }) + } + return nil + } + err := fdt.RootNode.Walk(addMemory) + if err != nil { + return nil, err + } + + reserveMemory := func(n *dt.Node) error { + p, found := n.LookProperty("reg") + if found { + r, err := p.AsRegion() + if err != nil { + return err + } + + mm.Insert(TypedRange{ + Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, + Type: RangeReserved, + }) + } + return nil + } + resv, found := fdt.NodeByName("reserved-memory") + if found { + err := resv.Walk(reserveMemory) + if err != nil { + return nil, err + } + } + + for _, r := range fdt.ReserveEntries { + mm.Insert(TypedRange{ + Range: Range{Start: uintptr(r.Address), Size: uint(r.Size)}, + Type: RangeReserved, + }) + } + + mm.sort() + mm.mergeAdjacent() + return mm, nil +} + +var memoryMapRoot = "/sys/firmware/memmap/" + +// MemoryMapFromSysfsMemmap reads a firmware-provided memory map from /sys/firmware/memmap. +// +// Linux support for this exists only on X86 at the time of this commit. +func MemoryMapFromSysfsMemmap() (MemoryMap, error) { + return memoryMapFromSysfsMemmap(memoryMapRoot) +} + +func memoryMapFromSysfsMemmap(memoryMapDir string) (MemoryMap, error) { + type memRange struct { + // start and end addresses are inclusive + start, end uintptr + typ RangeType + } + + ranges := make(map[string]memRange) + walker := func(name string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + const ( + // file names + start = "start" + end = "end" + typ = "type" + ) + + base := path.Base(name) + if base != start && base != end && base != typ { + return fmt.Errorf("unexpected file %q", name) + } + dir := path.Dir(name) + + b, err := os.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading file %q: %v", name, err) + } + + data := strings.TrimSpace(string(b)) + r := ranges[dir] + if base == typ { + typ, ok := sysfsToRangeType[data] + if !ok { + log.Printf("Sysfs file %q contains unrecognized memory map type %q, defaulting to Reserved", name, data) + r.typ = RangeReserved + } else { + r.typ = typ + } + ranges[dir] = r + return nil + } + + v, err := strconv.ParseUint(data, 0, strconv.IntSize) + if err != nil { + return err + } + switch base { + case start: + r.start = uintptr(v) + case end: + r.end = uintptr(v) + } + ranges[dir] = r + return nil + } + + if err := filepath.Walk(memoryMapDir, walker); err != nil { + return nil, err + } + + var mm MemoryMap + for _, r := range ranges { + // Range's end address is exclusive, while Linux's sysfs prints + // the end address inclusive. + // + // E.g. sysfs will contain + // + // start: 0x100, end: 0x1ff + // + // while we represent + // + // start: 0x100, size: 0x100. + mm = append(mm, TypedRange{ + Range: RangeFromInclusiveInterval(r.start, r.end), + Type: r.typ, + }) + } + mm.sort() + mm.mergeAdjacent() + return mm, nil +} + +// UEFIPayloadMemType are types used with LinuxBoot UEFI payload memory maps. +type UEFIPayloadMemType uint32 + +// Payload memory type (PayloadMemType) in UEFI payload. +const ( + UEFIPayloadTypeRAM UEFIPayloadMemType = 1 + UEFIPayloadTypeDefault UEFIPayloadMemType = 2 + UEFIPayloadTypeACPI UEFIPayloadMemType = 3 + UEFIPayloadTypeNVS UEFIPayloadMemType = 4 + UEFIPayloadTypeReserved UEFIPayloadMemType = 5 +) + +// UEFIPayloadMemoryMapEntry represent a memory map entry for a LinuxBoot UEFI payload. +type UEFIPayloadMemoryMapEntry struct { + Start uint64 + End uint64 + Type UEFIPayloadMemType +} + +// UEFIPayloadMemoryMap is a memory map used with LinuxBoot's UEFI payload. +type UEFIPayloadMemoryMap []UEFIPayloadMemoryMapEntry + +var rangeTypeToUEFIPayloadMemType = map[RangeType]UEFIPayloadMemType{ + RangeRAM: UEFIPayloadTypeRAM, + RangeDefault: UEFIPayloadTypeDefault, + RangeACPI: UEFIPayloadTypeACPI, + RangeNVS: UEFIPayloadTypeNVS, + RangeReserved: UEFIPayloadTypeReserved, +} + +func convertToUEFIPayloadMemType(rt RangeType) UEFIPayloadMemType { + mt, ok := rangeTypeToUEFIPayloadMemType[rt] + if !ok { + // return reserved if range type is not recognized + return UEFIPayloadTypeReserved + } + return mt +} + +// ToUEFIPayloadMemoryMap converts MemoryMap to a UEFI payload memory map. +func (mm MemoryMap) ToUEFIPayloadMemoryMap() UEFIPayloadMemoryMap { + var p UEFIPayloadMemoryMap + for _, entry := range mm { + p = append(p, UEFIPayloadMemoryMapEntry{ + Start: uint64(entry.Start), + End: uint64(entry.Start) + uint64(entry.Size) - 1, + Type: convertToUEFIPayloadMemType(entry.Type), + }) + } + return p +} + +// MemoryMapFromIOMem reads the kernel-maintained memory map from /proc/iomem. +func MemoryMapFromIOMem() (MemoryMap, error) { + return memoryMapFromIOMemFile("/proc/iomem") +} + +func rangeType(s string) RangeType { + if s == "reserved" { + return RangeReserved + } + return RangeType(s) +} + +func memoryMapFromIOMem(r io.Reader) (MemoryMap, error) { + var mm MemoryMap + b := bufio.NewScanner(r) + for b.Scan() { + // Format: + // 740100000000-7401001fffff : PCI Bus 0001:01 + els := strings.Split(b.Text(), ":") + if len(els) != 2 { + continue + } + typ := strings.TrimSpace(els[1]) + addrs := strings.Split(strings.TrimSpace(els[0]), "-") + if len(addrs) != 2 { + continue + } + start, err := strconv.ParseUint(addrs[0], 16, 64) + if err != nil { + continue + } + end, err := strconv.ParseUint(addrs[1], 16, 64) + if err != nil { + continue + } + // Special case -- empty ranges are represented as "000-000" + // even though the non-inclusive end would make that a 1-sized + // region. + if start == end { + continue + } + mm.Insert(TypedRange{ + Range: RangeFromInclusiveInterval(uintptr(start), uintptr(end)), + Type: rangeType(typ), + }) + } + if err := b.Err(); err != nil { + return nil, err + } + mm.sort() + mm.mergeAdjacent() + return mm, nil +} + +func memoryMapFromIOMemFile(path string) (MemoryMap, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + return memoryMapFromIOMem(f) +} + +func rangeFromMemblockLine(s string) *Range { + // Format: + // 0: 0x0000004000000000..0x00000040113fffff + els := strings.Split(s, ":") + if len(els) != 2 { + return nil + } + addrs := strings.Split(strings.TrimSpace(els[1]), "..") + if len(addrs) != 2 { + return nil + } + startS, _ := strings.CutPrefix(addrs[0], "0x") + start, err := strconv.ParseUint(startS, 16, 64) + if err != nil { + return nil + } + endS, _ := strings.CutPrefix(addrs[1], "0x") + end, err := strconv.ParseUint(endS, 16, 64) + if err != nil { + return nil + } + + // Special case -- empty ranges are represented as "000-000" + // even though the non-inclusive end would make that a 1-sized + // region. + if start == end { + return nil + } + + // end is inclusive. + r := RangeFromInclusiveInterval(uintptr(start), uintptr(end)) + return &r +} + +// MemoryMapFromMemblock reads a kernel-maintained memory map from /sys/kernel/debug/memblock. +// +// memblock is only available on kernels with CONFIG_ARCH_KEEP_MEMBLOCK (and +// debugfs). Without it, the kernel only maintains memblock early during init +// as its memory allocation mechanism. +func MemoryMapFromMemblock() (MemoryMap, error) { + m, err := os.Open("/sys/kernel/debug/memblock/memory") + if err != nil { + return nil, err + } + defer m.Close() + + r, err := os.Open("/sys/kernel/debug/memblock/reserved") + if err != nil { + return nil, err + } + defer r.Close() + + return memoryMapFromMemblock(m, r) +} + +func memoryMapFromMemblock(memory io.Reader, reserved io.Reader) (MemoryMap, error) { + var mm MemoryMap + b := bufio.NewScanner(memory) + for b.Scan() { + r := rangeFromMemblockLine(b.Text()) + if r == nil { + continue + } + mm.Insert(TypedRange{ + Range: *r, + Type: RangeRAM, + }) + } + if err := b.Err(); err != nil { + return nil, err + } + + b = bufio.NewScanner(reserved) + for b.Scan() { + r := rangeFromMemblockLine(b.Text()) + if r == nil { + continue + } + mm.Insert(TypedRange{ + Range: *r, + Type: RangeReserved, + }) + } + if err := b.Err(); err != nil { + return nil, err + } + mm.sort() + mm.mergeAdjacent() + return mm, nil +} diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go new file mode 100644 index 0000000000..0da6b79c2e --- /dev/null +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -0,0 +1,463 @@ +// Copyright 2018-2019 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kexec + +import ( + "fmt" + "os" + "path" + "reflect" + "strings" + "testing" + + "github.com/u-root/u-root/pkg/dt" +) + +func checkMemoryMap(t *testing.T, got, want MemoryMap) { + t.Helper() + if len(got) != len(want) { + t.Errorf("got memory map length %d, want memory map length %d", len(got), len(want)) + } + for idx, r := range got { + if r.Type != want[idx].Type { + t.Errorf("got memory at index %d type %v, want type %v", idx, r.Type, want[idx].Type) + } + if r.Range.Start != want[idx].Start || r.Size != want[idx].Size { + t.Errorf("got memory at index %d range %v, want range %v", idx, r.Range, want[idx].Range) + } + } +} + +func TestMemoryMapFromFDT(t *testing.T) { + for _, tc := range []struct { + name string + fdt *dt.FDT + wantMap MemoryMap + wantErr error + }{ + { + "empty", + &dt.FDT{RootNode: &dt.Node{Name: "/"}}, + MemoryMap{}, + nil, + }, + { + "add system memory ok", + &dt.FDT{ + RootNode: &dt.Node{ + Name: "/", + Children: []*dt.Node{ + { + Name: "test memory", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 2", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 3", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + }, + }, + }, + MemoryMap{ + TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffffffff}, "System RAM"}, + TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, + TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0x2ffffffffffff}, "System RAM"}, + }, + nil, + }, + { + "add system memory, and reserved memory ok", + &dt.FDT{ + RootNode: &dt.Node{ + Name: "/", + Children: []*dt.Node{ + { + Name: "test memory", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 2", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 3", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "reserved-memory", + Properties: []dt.Property{ + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, + }, + Children: []*dt.Node{ + { + Name: "reserved mem child node", + Properties: []dt.Property{ + { + "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + }, + }, + }, + }, + }, + }, + }, + MemoryMap{ + TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". + TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, + TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". + }, + nil, + }, + { + "add system memory, reserved memory, and reserved entries ok", + &dt.FDT{ + ReserveEntries: []dt.ReserveEntry{ + { + Address: uint64(0x1000000000000), + Size: uint64(0xffff), + }, + }, + RootNode: &dt.Node{ + Name: "/", + Children: []*dt.Node{ + { + Name: "test memory", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 2", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 3", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "reserved-memory", + Properties: []dt.Property{ + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, + }, + Children: []*dt.Node{ + { + Name: "reserved mem child node", + Properties: []dt.Property{ + { + "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + }, + }, + }, + }, + }, + }, + }, + MemoryMap{ + TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". + TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0xffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0x100000000ffff), Size: 0x1ffffffff0000}, "System RAM"}, // carve out reserve entry. + TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". + }, + nil, + }, + } { + t.Run(tc.name, func(t *testing.T) { + mm, err := MemoryMapFromFDT(tc.fdt) + if err != tc.wantErr { + t.Errorf("MemoryMapFromFDT returned error %v, want error %v", err, tc.wantErr) + } + checkMemoryMap(t, mm, tc.wantMap) + }) + } +} + +func TestMemoryMapFromSysfsMemmap(t *testing.T) { + root := t.TempDir() + + create := func(dir string, start, end uintptr, typ RangeType) error { + p := path.Join(root, dir) + if err := os.Mkdir(p, 0o755); err != nil { + return err + } + if err := os.WriteFile(path.Join(p, "start"), []byte(fmt.Sprintf("%#x\n", start)), 0o655); err != nil { + return err + } + if err := os.WriteFile(path.Join(p, "end"), []byte(fmt.Sprintf("%#x\n", end)), 0o655); err != nil { + return err + } + return os.WriteFile(path.Join(p, "type"), append([]byte(typ), '\n'), 0o655) + } + + if err := create("0", 0, 49, RangeRAM); err != nil { + t.Fatal(err) + } + if err := create("1", 100, 149, RangeACPI); err != nil { + t.Fatal(err) + } + if err := create("2", 200, 249, RangeNVS); err != nil { + t.Fatal(err) + } + if err := create("3", 300, 349, RangeReserved); err != nil { + t.Fatal(err) + } + + want := MemoryMap{ + {Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, + {Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, + {Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, + {Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, + } + + phys, err := memoryMapFromSysfsMemmap(root) + if err != nil { + t.Fatalf("MemoryMapFromSysfsMemmap() error: %v", err) + } + if !reflect.DeepEqual(phys, want) { + t.Errorf("MemoryMapFromSysfsMemmap() got %v, want %v", phys, want) + } +} + +func TestToUEFIPayloadMemoryMap(t *testing.T) { + mm := MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, + TypedRange{Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, + TypedRange{Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeRAM}, + } + want := UEFIPayloadMemoryMap{ + {Start: 0, End: 49, Type: UEFIPayloadTypeRAM}, + {Start: 100, End: 149, Type: UEFIPayloadTypeACPI}, + {Start: 200, End: 249, Type: UEFIPayloadTypeNVS}, + {Start: 300, End: 349, Type: UEFIPayloadTypeReserved}, + {Start: 400, End: 449, Type: UEFIPayloadTypeRAM}, + } + uefiMM := mm.ToUEFIPayloadMemoryMap() + if !reflect.DeepEqual(uefiMM, want) { + t.Errorf("ToUEFIPayloadMemoryMap() got %v, want %v", uefiMM, want) + } +} + +func TestMemoryMapInsert(t *testing.T) { + for i, tt := range []struct { + mm MemoryMap + r TypedRange + want MemoryMap + }{ + { + // r is entirely within m's one range. + mm: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x2000}, Type: RangeRAM}, + }, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 0x200, Size: 0x2000 - 0x200}, Type: RangeRAM}, + }, + }, + { + // r sits across three RAM ranges. + mm: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x150}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x150, Size: 0x50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x1a0, Size: 0x100}, Type: RangeRAM}, + }, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 0x200, Size: 0xa0}, Type: RangeRAM}, + }, + }, + { + // r is a superset of the ranges in m. + mm: MemoryMap{ + TypedRange{Range: Range{Start: 0x100, Size: 0x50}, Type: RangeRAM}, + }, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + }, + }, + { + // r is the first range in the map. + mm: MemoryMap{}, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + }, + }, + } { + t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { + // Make a copy for the Errorf print. + mm := tt.mm + tt.mm.Insert(tt.r) + + if !reflect.DeepEqual(tt.mm, tt.want) { + t.Errorf("\n%v.Insert(%s) =\n%v, want\n%v", mm, tt.r, tt.mm, tt.want) + } + }) + } +} + +func TestMemoryMapFromIOMem(t *testing.T) { + f := `10000000-101fffff : reserved +10201000-10202fff : reserved +14000000-1effffff : System RAM + 14154000-14154fff : reserved + 141c0000-14bcffff : reserved + 14c10000-1636ffff : Kernel code + 16370000-1686ffff : reserved + 16870000-1734ffff : Kernel data + 17350000-17377fff : reserved` + mm, err := memoryMapFromIOMem(strings.NewReader(f)) + if err != nil { + t.Fatal(err) + } + + want := MemoryMap{ + TypedRange{Range: RangeFromInterval(0x10000000, 0x101fffff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x10201000, 0x10202fff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x14000000, 0x14154000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x14154000, 0x14154fff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x14155000, 0x141c0000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x141c0000, 0x14bcffff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x14bd0000, 0x14c10000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x14c10000, 0x1636ffff+1), Type: RangeType("Kernel code")}, + TypedRange{Range: RangeFromInterval(0x16370000, 0x1686ffff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x16870000, 0x1734ffff+1), Type: RangeType("Kernel data")}, + TypedRange{Range: RangeFromInterval(0x17350000, 0x17377fff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x17378000, 0x1effffff+1), Type: RangeRAM}, + } + if !reflect.DeepEqual(mm, want) { + t.Errorf("Not equal, got %v", mm) + } + + ignored := `00000000-00000000 : reserved +10000000-101fffff +10201000 : reserved +: System RAM + 141GGGGG-14154fff : reserved + 141c0000-14GGGGGG : reserved` + mm2, err := memoryMapFromIOMem(strings.NewReader(ignored)) + if err != nil { + t.Fatal(err) + } + + if want := MemoryMap(nil); !reflect.DeepEqual(mm2, want) { + t.Errorf("Memory maps not equal, got %v, want %v", mm2, want) + } +} + +func TestMemoryMapFromMemblock(t *testing.T) { + memory := ` 0: 0x0000004000000000..0x00000040113fffff + 1: 0x0000004011400000..0x00000040123fffff + 2: 0x0000004012400000..0x00000040dfffffff + 3: 0x0000004400000000..0x00000044dfffffff` + reserved := ` 0: 0x0000004000000000..0x00000040113fffff + 1: 0x0000004012400000..0x00000040125fffff + 2: 0x0000004012800000..0x00000040137fffff` + mm, err := memoryMapFromMemblock(strings.NewReader(memory), strings.NewReader(reserved)) + if err != nil { + t.Fatal(err) + } + + want := MemoryMap{ + TypedRange{Range: RangeFromInterval(0x4000000000, 0x4011400000), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x4011400000, 0x4012400000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x4012400000, 0x4012600000), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x4012600000, 0x4012800000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x4012800000, 0x4013800000), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x4013800000, 0x40e0000000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x4400000000, 0x44e0000000), Type: RangeRAM}, + } + if !reflect.DeepEqual(mm, want) { + t.Errorf("Not equal, got %v", mm) + } + + memIgnored := ` 0: 0x0000000000000000..0x0000000000000000 + 0: 0x0000004000000000.. + 1: 0x0000004011400000..0x00000040GGGGGGGG + 0x0000004012400000..0x00000040dfffffff + 2: 0x000000401GGGGGGG..0x00000040dfffffff + 3: 0x00000044000000000x00000044dfffffff` + reservedIgnored := ` 0: 0x0000004000000000.. + 1: 0x0000004011400000..0x00000040GGGGGGGG + 0x0000004012400000..0x00000040dfffffff + 2: 0x000000401GGGGGGG..0x00000040dfffffff + 3: 0x00000044000000000x00000044dfffffff` + mm2, err := memoryMapFromMemblock(strings.NewReader(memIgnored), strings.NewReader(reservedIgnored)) + if err != nil { + t.Fatal(err) + } + + if want := MemoryMap(nil); !reflect.DeepEqual(mm2, want) { + t.Errorf("Memory maps not equal, got %v, want %v", mm2, want) + } +} + +func TestMemoryMapMerge(t *testing.T) { + mm := MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 50, Size: 20}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 70, Size: 40}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 111, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 121, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 500, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 500, Size: 20}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 600, Size: 20}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 600, Size: 50}, Type: RangeReserved}, + } + + want := MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 110}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 111, Size: 60}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 500, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 600, Size: 50}, Type: RangeReserved}, + } + + mm.mergeAdjacent() + if !reflect.DeepEqual(mm, want) { + t.Errorf("Merge() got %v, want %v", mm, want) + } +} diff --git a/pkg/boot/linux.go b/pkg/boot/linux.go index 1292e272b8..cc0f64b8da 100644 --- a/pkg/boot/linux.go +++ b/pkg/boot/linux.go @@ -5,7 +5,6 @@ package boot import ( - "encoding/json" "errors" "fmt" "io" @@ -16,8 +15,7 @@ import ( "github.com/u-root/u-root/pkg/boot/linux" "github.com/u-root/u-root/pkg/boot/util" "github.com/u-root/u-root/pkg/mount" - "github.com/u-root/u-root/pkg/uio" - "github.com/u-root/uio/ulog" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) @@ -30,99 +28,22 @@ type LinuxImage struct { Cmdline string BootRank int LoadSyscall bool + DTB io.ReaderAt - KexecOpts linux.KexecOptions -} - -// LoadedLinuxImage is a processed version of LinuxImage. -// -// Main difference being that kernel and initrd is made as -// a read-only *os.File. There is also additional processing -// such as DTB, if available under KexecOpts, will be appended -// to Initrd. -type LoadedLinuxImage struct { - Name string - Kernel *os.File - Initrd *os.File - Cmdline string - LoadSyscall bool - KexecOpts linux.KexecOptions -} - -// loadedLinuxImageJSON is same as LoadedLinuxImage, but with transformed fields to help with serialization of LoadedLinuxImage. -type loadedLinuxImageJSON struct { - Name string - KernelPath string - InitrdPath string - Cmdline string - LoadSyscall bool - KexecOpts linux.KexecOptions + // ReservedRanges are additional physical memory pieces that will be + // avoided when allocating kexec segments. Only used for LoadSyscall. + // + // ReservedRanges will not be shared with the next kernel, which is + // free to use this memory unless some other mechanism (such as + // memmap=) reserves it. + ReservedRanges kexec.Ranges } var _ OSImage = &LinuxImage{} var errNilKernel = errors.New("kernel image is empty, nothing to execute") -// MarshalJSON customizes marshaling for LoadedLinuxImage. It handles serializations -// for *os.File, so that kernel and initrd can be unmarshalled properly. -func (lli *LoadedLinuxImage) MarshalJSON() ([]byte, error) { - lliJSON := loadedLinuxImageJSON{} - // Sync and close kernel and initrd File object, and marshal paths to the files. - if lli.Kernel != nil { - if err := lli.Kernel.Sync(); err != nil { - return nil, err - } - lliJSON.KernelPath = lli.Kernel.Name() - if err := lli.Kernel.Close(); err != nil { - return nil, err - } - } - if lli.Initrd != nil { - if err := lli.Initrd.Sync(); err != nil { - return nil, err - } - lliJSON.InitrdPath = lli.Initrd.Name() - if err := lli.Initrd.Close(); err != nil { - return nil, err - } - } - lliJSON.Name = lli.Name - lliJSON.Cmdline = lli.Cmdline - lliJSON.LoadSyscall = lli.LoadSyscall - lliJSON.KexecOpts = lli.KexecOpts - - return json.Marshal(lliJSON) -} - -// UnmarshalJSON customizes unmarshaling for LoadedLinuxImage. It processes kernel -// and initrd file by name, and opens a read-only copies for further execution. -func (lli *LoadedLinuxImage) UnmarshalJSON(b []byte) error { - lliJSON := loadedLinuxImageJSON{} - if err := json.Unmarshal(b, &lliJSON); err != nil { - return err - } - if len(strings.TrimSpace(lliJSON.KernelPath)) > 0 { - readOnlyK, err := os.Open(lliJSON.KernelPath) - if err != nil { - return err - } - lli.Kernel = readOnlyK - } - if len(strings.TrimSpace(lliJSON.InitrdPath)) > 0 { - readOnlyI, err := os.Open(lliJSON.InitrdPath) - if err != nil { - return err - } - lli.Initrd = readOnlyI - } - lli.Name = lliJSON.Name - lli.Cmdline = lliJSON.Cmdline - lli.LoadSyscall = lliJSON.LoadSyscall - lli.KexecOpts = lliJSON.KexecOpts - return nil -} - -// named is satisifed by both *os.File and *vfile.File. Hack hack hack. +// named is satisifed by *os.File. type named interface { Name() string } @@ -151,10 +72,10 @@ func (li *LinuxImage) Label() string { fmt.Sprintf("initrd=%s", stringer(li.Initrd)), ) } - if li.KexecOpts.DTB != nil { + if li.DTB != nil { labelInfo = append( labelInfo, - fmt.Sprintf("dtb=%s", stringer(li.KexecOpts.DTB)), + fmt.Sprintf("dtb=%s", stringer(li.DTB)), ) } @@ -169,8 +90,8 @@ func (li *LinuxImage) Rank() int { // String prints a human-readable version of this linux image. func (li *LinuxImage) String() string { return fmt.Sprintf( - "LinuxImage(\n Name: %s\n Kernel: %s\n Initrd: %s\n Cmdline: %s\n KexecOpts: %v\n)\n", - li.Name, stringer(li.Kernel), stringer(li.Initrd), li.Cmdline, li.KexecOpts, + "LinuxImage(\n Name: %s\n Kernel: %s\n Initrd: %s\n Cmdline: %s\n DTB: %v\n)\n", + li.Name, stringer(li.Kernel), stringer(li.Initrd), li.Cmdline, stringer(li.DTB), ) } @@ -268,97 +189,39 @@ func CopyToFileIfNotRegular(r io.ReaderAt, verbose bool) (*os.File, error) { return readOnlyF, nil } -// loadLinuxImage processes given LinuxImage, and make it ready for kexec. -// -// For example: -// -// - Acquiring a read-only copy of kernel and initrd as kernel -// don't like them being opened for writting by anyone while -// executing. -// - Append DTB, if present to end of initrd. -func loadLinuxImage(li *LinuxImage, logger ulog.Logger, verbose bool) (*LoadedLinuxImage, func(), error) { +// Edit the kernel command line. +func (li *LinuxImage) Edit(f func(cmdline string) string) { + li.Cmdline = f(li.Cmdline) +} + +func (li *LinuxImage) loadImage(loadOpts *loadOptions) (*os.File, *os.File, error) { if li.Kernel == nil { return nil, nil, errNilKernel } - k, err := CopyToFileIfNotRegular(util.TryGzipFilter(li.Kernel), verbose) + k, err := CopyToFileIfNotRegular(util.TryGzipFilter(li.Kernel), loadOpts.verbose) if err != nil { return nil, nil, err } // Append device-tree file to the end of initrd. - if li.KexecOpts.DTB != nil { + if li.DTB != nil { if li.Initrd != nil { - li.Initrd = CatInitrds(li.Initrd, li.KexecOpts.DTB) + li.Initrd = CatInitrds(li.Initrd, li.DTB) } else { - li.Initrd = li.KexecOpts.DTB + li.Initrd = li.DTB } } var i *os.File if li.Initrd != nil { - i, err = CopyToFileIfNotRegular(li.Initrd, verbose) + i, err = CopyToFileIfNotRegular(li.Initrd, loadOpts.verbose) if err != nil { + k.Close() return nil, nil, err } } - - logger.Printf("Kernel: %s", k.Name()) - if i != nil { - logger.Printf("Initrd: %s", i.Name()) - } - logger.Printf("Command line: %s", li.Cmdline) - logger.Printf("KexecOpts: %#v", li.KexecOpts) - - cleanup := func() { - k.Close() - i.Close() - } - - return &LoadedLinuxImage{ - Name: li.Name, - Kernel: k, - Initrd: i, - Cmdline: li.Cmdline, - LoadSyscall: li.LoadSyscall, - KexecOpts: li.KexecOpts, - }, cleanup, nil -} - -// saveLoadedLinuxImage marshals a LoadedLinuxImage to a json file. -// -// Marshalling it to a json versus a binary format, makes it readable -// and easier to tamper in case when we need change a field or two -// for experiments and debug. -// -// With that said, it is obvious that this saved info can then be -// loaded by a later kexec for further execution from an already loaded -// linux image and original load options. -func saveLoadedLinuxImage(lli *LoadedLinuxImage, p string) error { - f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) - if err != nil { - return err - } - out, err := json.Marshal(lli) - if err != nil { - return err - } - nw, err := f.Write(out) - if err != nil { - return err - } - if nw != len(out) { - return fmt.Errorf("written %d bytes, want %d bytes", nw, len(out)) - } - if err := f.Sync(); err != nil { - return err - } - return f.Close() -} - -// Edit the kernel command line. -func (li *LinuxImage) Edit(f func(cmdline string) string) { - li.Cmdline = f(li.Cmdline) + return k, i, nil } // Load implements OSImage.Load and kexec_load's the kernel with its initramfs. @@ -368,20 +231,27 @@ func (li *LinuxImage) Load(opts ...LoadOption) error { opt(loadOpts) } - loadedImage, cleanup, err := loadLinuxImage(li, loadOpts.logger, loadOpts.verbose) + k, i, err := li.loadImage(loadOpts) if err != nil { return err } - defer cleanup() + defer k.Close() + if i != nil { + defer i.Close() + } + + loadOpts.logger.Printf("Kernel: %s", k.Name()) + if i != nil { + loadOpts.logger.Printf("Initrd: %s", i.Name()) + } + loadOpts.logger.Printf("Command line: %s", li.Cmdline) + loadOpts.logger.Printf("DTB: %#v", li.DTB) if !loadOpts.callKexecLoad { - // If dryRun, serializes previously loaded linuxImage info to a file in tmpfs. - // The info can be re-loaded for later kexec execution. It works b/c kernel and - // initrd are already downloaded and saved into tmpfs. - return saveLoadedLinuxImage(loadedImage, loadOpts.linuxImageCfgFile) + return nil } if li.LoadSyscall { - return linux.KexecLoad(loadedImage.Kernel, loadedImage.Initrd, loadedImage.Cmdline, loadedImage.KexecOpts) + return linux.KexecLoad(k, i, li.Cmdline, li.DTB, li.ReservedRanges) } - return kexec.FileLoad(loadedImage.Kernel, loadedImage.Initrd, loadedImage.Cmdline) + return kexec.FileLoad(k, i, li.Cmdline) } diff --git a/pkg/boot/linux/doc.go b/pkg/boot/linux/doc.go index 546d34ef8c..fb5c8820d2 100644 --- a/pkg/boot/linux/doc.go +++ b/pkg/boot/linux/doc.go @@ -99,13 +99,14 @@ // those seven quadwords can be used for parameter passing. // // These changes should let us: -// o build the purgatory as a non-relative ELF, i.e. a statically linked program with one ELF program (segment) -// o and link it at 0x3000; the code was putting the current relative ELF in a fixed place anyway -// o use the ELF program header to tell us where to put the purgatory -// o communicate arguments in the seven quadwords mentioned above -// o rather than one does-it-all purgatory as we have today, we can provide several variants -// -// so we get one suited to the job at hand. +// - build the purgatory as a non-relative ELF, i.e. a statically linked +// program with one ELF program (segment) +// - and link it at 0x3000; the code was putting the current relative ELF in +// a fixed place anyway - use the ELF program header to tell us where to +// put the purgatory +// - communicate arguments in the seven quadwords mentioned above +// - rather than one does-it-all purgatory as we have today, we can provide +// several variants so we get one suited to the job at hand. // // This should result in a dramatically simpler purgatory implementation. Also, // being much simpler, it can be entirely Go assembly, obviating the need for a diff --git a/pkg/boot/linux/load_linux.go b/pkg/boot/linux/load_linux.go new file mode 100644 index 0000000000..06d6e27c61 --- /dev/null +++ b/pkg/boot/linux/load_linux.go @@ -0,0 +1,55 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linux + +import ( + "fmt" + "io" + "os" + "syscall" + + "github.com/u-root/uio/uio" + "golang.org/x/sys/unix" +) + +func mmap(f *os.File) ([]byte, func() error, error) { + s, err := f.Stat() + if err != nil { + return nil, nil, fmt.Errorf("stat error: %w", err) + } + if s.Size() == 0 { + return nil, nil, fmt.Errorf("%w: cannot mmap zero-len file", os.ErrInvalid) + } + d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE) + if err != nil { + return nil, nil, fmt.Errorf("mmap failed: %w", err) + } + + ummap := func() error { + if err := unix.Munmap(d); err != nil { + return fmt.Errorf("failed to unmap %s: %w", f.Name(), err) + } + return nil + } + return d, ummap, nil +} + +func getFile(f *os.File) ([]byte, func() error, error) { + if d, unmap, err := mmap(f); err == nil { + return d, unmap, nil + } + var d []byte + var err error + // Pipes and other files like that will fail to seek. + if _, serr := f.Seek(0, 0); serr != nil { + d, err = io.ReadAll(f) + } else { + d, err = uio.ReadAll(f) + } + if err != nil { + return nil, nil, fmt.Errorf("failed to read kernel file: %w", err) + } + return d, func() error { return nil }, nil +} diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index 7cbc58783e..44cd72007f 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/boot/bzimage" "github.com/u-root/u-root/pkg/boot/kexec" "github.com/u-root/u-root/pkg/boot/purgatory" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const ( @@ -25,7 +25,7 @@ const ( // kernel with the given ramfs file and cmdline string. // // It uses the kexec_load system call. -func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservations kexec.Ranges) error { bzimage.Debug = Debug // A collection of vars used for processing the kernel for kexec @@ -72,13 +72,19 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error Debug("kernelEntry: %v", kernelEntry) // Prepare segments. - kmem = &kexec.Memory{} Debug("Try parsing memory map...") // TODO(10000TB): refactor this call into initialization of // kexec.Memory, as it does not depend on specific boot. - if err := kmem.ParseMemoryMap(); err != nil { + mm, err := kexec.MemoryMapFromSysfsMemmap() + if err != nil { return fmt.Errorf("parse memory map: %v", err) } + for _, r := range reservations { + mm.Insert(kexec.TypedRange{Range: r, Type: kexec.RangeReserved}) + } + kmem = &kexec.Memory{ + Phys: mm, + } var relocatableKernel bool if bzimg.Header.Protocolversion < 0x0205 { @@ -97,10 +103,16 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error var ramfsRange kexec.Range if ramfs != nil { - ramfsContents, err := io.ReadAll(ramfs) + ramfsContents, cleanup, err := getFile(ramfs) if err != nil { return fmt.Errorf("unable to read initramfs: %w", err) } + defer func() { + if err := cleanup(); err != nil { + Debug("Failed to clean up initramfs: %v", err) + } + }() + if ramfsRange, err = kmem.AddKexecSegment(ramfsContents); err != nil { return fmt.Errorf("add initramfs segment: %w", err) } @@ -132,16 +144,13 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error return fmt.Errorf("re-marshaling header: %w", err) } - // TODO(10000TB): Free mem hole start end aligns by - // max(16, pagesize). - // - // Push alignment logic to kexec memory functions, e.g. a similar - // function to FindSpace. setupRange, err := kmem.AddPhysSegment( linuxParam, + // We use Linux's 32bit/64bit entry point, so we can place the + // setup range anywhere in the low 4G. kexec.RangeFromInterval( - uintptr(0x90000), - uintptr(len(linuxParam)), + uintptr(4096), + uintptr(1<<32-1), ), // TODO(10000TB): evaluate if we need to provide option to // reserve from end. diff --git a/pkg/boot/linux/load_linux_arm64.go b/pkg/boot/linux/load_linux_arm64.go index 1dd20a0932..55c762bbb4 100644 --- a/pkg/boot/linux/load_linux_arm64.go +++ b/pkg/boot/linux/load_linux_arm64.go @@ -5,223 +5,26 @@ package linux import ( - "bytes" - "encoding/binary" "fmt" + "io" "os" - "syscall" - "github.com/u-root/u-root/pkg/boot/image" "github.com/u-root/u-root/pkg/boot/kexec" - "github.com/u-root/u-root/pkg/dt" - "github.com/u-root/u-root/pkg/uio" - "golang.org/x/sys/unix" ) -const ( - kernelAlignSize = 1 << 21 // 2 MB. -) - -func mmap(f *os.File) (data []byte, ummap func() error, err error) { - s, err := f.Stat() - if err != nil { - return nil, nil, fmt.Errorf("stat error: %w", err) - } - if s.Size() == 0 { - return nil, nil, fmt.Errorf("cannot mmap zero-len file") - } - d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE) - if err != nil { - return nil, nil, fmt.Errorf("mmap failed: %w", err) - } - - ummap = func() error { - return unix.Munmap(d) - } - - return d, ummap, nil -} - -// sanitizeFDT cleanups boot param properties from chosen node of the given FDT. -func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { - // Clear old entries in case we've already been through kexec to get - // to this instance of runtime. - chosen, _ := fdt.NodeByName("chosen") - if chosen == nil { - return nil, fmt.Errorf("no /chosen node in device tree") - } - for _, property := range []string{"linux,elfcorehdr", "linux,usable-memory-range", "kaslr-seed", "rng-seed", "linux,initrd-start", "linux,initrd-end"} { - chosen.RemoveProperty(property) - } - - return chosen, nil -} - // KexecLoad loads arm64 Image, with the given ramfs and kernel cmdline. -func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { - var err error - // kmem is a struct holding kexec segments. - // - // It has routines to work with physical memory - // ranges. - var kmem *kexec.Memory - var kernelRange, ramfsRange, dtbRange, trampolineRange kexec.Range - - fdt, err := dt.LoadFDT(opts.DTB) - if err != nil { - return fmt.Errorf("loadFDT(%s) = %v", opts.DTB, err) - } - Debug("Loaded FDT: %s", fdt) - - chosen, err := sanitizeFDT(fdt) - if err != nil { - return fmt.Errorf("sanitizeFDT(%v) = %v", fdt, err) - } - Debug("FDT after sanitization: %s", fdt) - - // Prepare segments. - kmem = &kexec.Memory{} - Debug("Try parsing memory map...") - if err := kmem.ParseMemoryMapFromFDT(fdt); err != nil { - return fmt.Errorf("ParseMemoryMapFromFDT(%v): %v", fdt, err) - } - Debug("Mem map: \n%+v", kmem.Phys) - - // Load kernel. - var kernelBuf []byte - if opts.MmapKernel { - Debug("Mmapping kernel to virtual buffer...") - var cleanup func() error - kernelBuf, cleanup, err = mmap(kernel) - if err != nil { - return fmt.Errorf("mmap kernel: %v", err) - } - defer func() { - if err = cleanup(); err != nil { - Debug("Ummap kernel failed: %v", err) - } - }() - } else { - Debug("Read kernel from file ...") - kernelBuf, err = uio.ReadAll(kernel) - if err != nil { - return fmt.Errorf("read kernel from file: %v", err) - } - } - - kImage, err := image.ParseFromBytes(kernelBuf) - if err != nil { - return fmt.Errorf("parse arm64 Image from bytes: %v", err) - } - - if kernelRange, err = kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize+kImage.Header.TextOffset), uint(kImage.Header.TextOffset), kernelAlignSize); err != nil { - return fmt.Errorf("add kernel segment: %v", err) - } - - Debug("Added %d byte (size %d) kernel at %s", len(kernelBuf), kImage.Header.ImageSize, kernelRange) - - var ramfsBuf []byte - if ramfs != nil { - if opts.MmapRamfs { - Debug("Mmap ramfs file to virtual buffer...") - var cleanup func() error - ramfsBuf, cleanup, err = mmap(ramfs) - if err != nil { - return fmt.Errorf("mmap ramfs: %v", err) - } - defer func() { - if err = cleanup(); err != nil { - Debug("Ummap ramfs failed: %v", err) - } - }() - } else { - Debug("Read ramfs from file...") - ramfsBuf, err = uio.ReadAll(ramfs) - if err != nil { - return fmt.Errorf("read ramfs from file: %v", err) - } - } - } - - // NOTE(10000TB): This need be placed after kernel by convention. - if ramfsRange, err = kmem.AddKexecSegment(ramfsBuf); err != nil { - return fmt.Errorf("add initramfs segment: %v", err) - } - Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange) - - ramfsStart := make([]byte, 8) - binary.BigEndian.PutUint64(ramfsStart, uint64(ramfsRange.Start)) - chosen.UpdateProperty("linux,initrd-start", ramfsStart) - ramfsEnd := make([]byte, 8) - binary.BigEndian.PutUint64(ramfsEnd, uint64(ramfsRange.Start)+uint64(ramfsRange.Size)) - chosen.UpdateProperty("linux,initrd-end", ramfsEnd) - - Debug("Kernel cmdline to append: %s", cmdline) - if len(cmdline) > 0 { - cmdlineBuf := append([]byte(cmdline), byte(0)) - chosen.UpdateProperty("bootargs", cmdlineBuf) - } else { - chosen.RemoveProperty("bootargs") - } - - dtbBuffer := &bytes.Buffer{} - _, err = fdt.Write(dtbBuffer) +// +// reservedRanges are additional pieces of physical memory that are not used +// for kexec segment allocation. They are not transmitted to the next kernel to +// be considered reserved. +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservedRanges kexec.Ranges) error { + img, err := kexecLoadImage(kernel, ramfs, cmdline, dtb, reservedRanges) if err != nil { - return fmt.Errorf("flattening device tree: %v", err) + return err } - dtbBuf := dtbBuffer.Bytes() - if dtbRange, err = kmem.AddKexecSegment(dtbBuf); err != nil { - return fmt.Errorf("add device tree segment: %w", err) + defer img.clean() + if err = kexec.Load(img.entry, img.segments, 0); err != nil { + return fmt.Errorf("kexec Load(%v, %v, %d) = %v", img.entry, img.segments, 0, err) } - Debug("Added %d byte device tree at %s", len(dtbBuf), dtbRange) - - // Trampoline. - // - // We need a trampoline to pass the DTB to the kernel; because - // we'll use this code as our entry point, it also needs to know - // the real entry point to kernel. - // - // TODO(10000TB): this assumes a little endian kernel, support - // big endian if needed per flag. - kernelEntry := kernelRange.Start - dtbBase := dtbRange.Start - - var trampoline [10]uint32 - // Instruction encoding per - // "Arm Architecture Reference Manual Armv8, for Armv8-A architecture - // profile" [ ARM DDI 0487E.a (ID070919) ] - trampoline[0] = 0x580000c4 // ldr x4, #0x18 (PC relative: trampoline[6 and 7]) - trampoline[1] = 0x580000e0 // ldr x0, #0x1c (PC relative: trampoline[8 and 9]) - // Zero out x1, x2, x3 - trampoline[2] = 0xaa1f03e1 // mov x1, xzr - trampoline[3] = 0xaa1f03e2 // mov x2, xzr - trampoline[4] = 0xaa1f03e3 // mov x3, xzr - // Branch register / Jump to instruction from x4. - trampoline[5] = 0xd61f0080 // br x4 - - trampoline[6] = uint32(uint64(kernelEntry) & 0xffffffff) - trampoline[7] = uint32(uint64(kernelEntry) >> 32) - trampoline[8] = uint32(uint64(dtbBase) & 0xffffffff) - trampoline[9] = uint32(uint64(dtbBase) >> 32) - - trampolineBuffer := new(bytes.Buffer) - err = binary.Write(trampolineBuffer, binary.LittleEndian, trampoline) - if err != nil { - return fmt.Errorf("make trampoline: %v", err) - } - Debug("trampoline bytes %x", trampolineBuffer.Bytes()) - trampolineRange, err = kmem.AddKexecSegment(trampolineBuffer.Bytes()) - if err != nil { - return fmt.Errorf("add trampoline segment: %v", err) - } - Debug("Added %d byte trampoline at %s", len(trampolineBuffer.Bytes()), trampolineRange) - - /* Load it */ - entry := trampolineRange.Start - Debug("Entry: %#x", entry) - if err = kexec.Load(entry, kmem.Segments, 0); err != nil { - return fmt.Errorf("kexec Load(%v, %v, %d) = %v", entry, kmem.Segments, 0, err) - } - return nil } diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go new file mode 100644 index 0000000000..3a1a20f36e --- /dev/null +++ b/pkg/boot/linux/load_linux_image.go @@ -0,0 +1,232 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linux + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "math" + "os" + + "github.com/u-root/u-root/pkg/boot/image" + "github.com/u-root/u-root/pkg/boot/kexec" + "github.com/u-root/u-root/pkg/dt" +) + +type kimage struct { + segments kexec.Segments + entry uintptr + cleanup []func() error +} + +func (k kimage) clean() { + for _, c := range k.cleanup { + if err := c(); err != nil { + Debug("Failure: %v", err) + } + } +} + +const ( + kernelAlignSize = 1 << 21 // 2 MB. +) + +var errNoChosenNode = fmt.Errorf("no /chosen node in device tree") + +// sanitizeFDT cleanups boot param properties from chosen node of the given FDT. +func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { + // Clear old entries in case we've already been through kexec to get + // to this instance of runtime. + chosen, _ := fdt.NodeByName("chosen") + if chosen == nil { + return nil, errNoChosenNode + } + for _, property := range []string{"linux,elfcorehdr", "linux,usable-memory-range", "kaslr-seed", "rng-seed", "linux,initrd-start", "linux,initrd-end"} { + chosen.RemoveProperty(property) + } + + return chosen, nil +} + +var ErrMemmapEmpty = errors.New("memory map is empty or contains no information about system RAM") + +func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservedRanges kexec.Ranges) (*kimage, error) { + var fdt *dt.FDT + var err error + // We want to fail when a user-supplied FDT is not parseable, not + // implicitly fall back to some other FDT. Avoid the dt.LoadFDT API. + if dtb != nil { + fdt, err = dt.ReadFDT(io.NewSectionReader(dtb, 0, math.MaxInt64)) + } else { + fdt, err = dt.ReadFile("/sys/firmware/fdt") + } + if err != nil { + return nil, fmt.Errorf("Read FDT = %w", err) + } + Debug("Loaded FDT: %s", fdt) + + // Prepare segments. + Debug("Try parsing memory map...") + mm, err := kexec.MemoryMapFromFDT(fdt) + if err != nil { + return nil, fmt.Errorf("MemoryMapFromFDT(%v): %w", fdt, err) + } + Debug("Mem map: \n%+v", mm) + if len(mm.RAM()) == 0 { + return nil, ErrMemmapEmpty + } + for _, r := range reservedRanges { + mm.Insert(kexec.TypedRange{Range: r, Type: kexec.RangeReserved}) + } + return kexecLoadImageMM(mm, kernel, ramfs, fdt, cmdline) +} + +var ( + errKernelSegmentFailed = errors.New("failed to add kernel segment") + errInitramfsSegmentFailed = errors.New("failed to add initramfs segment") + errDTBSegmentFailed = errors.New("failed to add DTB segment") + errTrampolineSegmentFailed = errors.New("failed to add trampolineSegment") +) + +func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, cmdline string) (*kimage, error) { + kmem := &kexec.Memory{ + Phys: mm, + } + + img := &kimage{} + + // Load kernel. + kernelBuf, cleanup, err := getFile(kernel) + if err != nil { + return nil, fmt.Errorf("failed to get kernel contents: %w", err) + } + img.cleanup = append(img.cleanup, cleanup) + + kImage, err := image.ParseFromBytes(kernelBuf) + if err != nil { + return nil, fmt.Errorf("parse arm64 Image from bytes: %w", err) + } + + // "The Image must be placed text_offset bytes from a 2MB aligned base + // address anywhere in usable system RAM and called there." + // (arm64/booting.rst) + // + // "At least image_size bytes from the start of the image must be free + // for use by the kernel." (arm64/booting.rst) + // + // TODO: support versions below v4.6? + // + // "NOTE: versions prior to v4.6 cannot make use of memory below the + // physical offset of the Image so it is recommended that the Image be + // placed as close as possible to the start of system RAM." + // (arm64/booting.rst) + kernelRange, err := kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize), uint(kImage.Header.TextOffset), kernelAlignSize) + if err != nil { + return nil, fmt.Errorf("%w: %w", errKernelSegmentFailed, err) + } + + Debug("Added %#x byte (size %#x) kernel at %s with offset %#x with alignment %#x", len(kernelBuf), kImage.Header.ImageSize, kernelRange, kImage.Header.TextOffset, kernelAlignSize) + + chosen, err := sanitizeFDT(fdt) + if err != nil { + return nil, fmt.Errorf("sanitizeFDT(%v) = %w", fdt, err) + } + Debug("FDT after sanitization: %s", fdt) + + if ramfs != nil { + ramfsBuf, cleanup, err := getFile(ramfs) + if err != nil { + return nil, fmt.Errorf("failed to get initramfs contents: %w", err) + } + img.cleanup = append(img.cleanup, cleanup) + + // NOTE(10000TB): This need be placed after kernel by convention. + // + // "If an initrd/initramfs is passed to the kernel at boot, it + // must reside entirely within a 1 GB aligned physical memory + // window of up to 32 GB in size that fully covers the kernel + // Image as well." (arm64/booting.rst) + ramfsRange, err := kmem.AddKexecSegment(ramfsBuf) + if err != nil { + return nil, fmt.Errorf("%w: %w", errInitramfsSegmentFailed, err) + } + Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange) + + ramfsStart := make([]byte, 8) + binary.BigEndian.PutUint64(ramfsStart, uint64(ramfsRange.Start)) + chosen.UpdateProperty("linux,initrd-start", ramfsStart) + ramfsEnd := make([]byte, 8) + binary.BigEndian.PutUint64(ramfsEnd, uint64(ramfsRange.Start)+uint64(ramfsRange.Size)) + chosen.UpdateProperty("linux,initrd-end", ramfsEnd) + } + + Debug("Kernel cmdline to append: %s", cmdline) + if len(cmdline) > 0 { + cmdlineBuf := append([]byte(cmdline), byte(0)) + chosen.UpdateProperty("bootargs", cmdlineBuf) + } else { + chosen.RemoveProperty("bootargs") + } + + var dtbBuffer bytes.Buffer + if _, err := fdt.Write(&dtbBuffer); err != nil { + return nil, fmt.Errorf("flattening device tree: %v", err) + } + dtbBuf := dtbBuffer.Bytes() + dtbRange, err := kmem.AddKexecSegment(dtbBuf) + if err != nil { + return nil, fmt.Errorf("%w: %w", errDTBSegmentFailed, err) + } + Debug("Added %d byte device tree at %s", len(dtbBuf), dtbRange) + + // Trampoline. + // + // We need a trampoline to pass the DTB to the kernel; because + // we'll use this code as our entry point, it also needs to know + // the real entry point to kernel. + // + // TODO(10000TB): this assumes a little endian kernel, support + // big endian if needed per flag. + kernelEntry := kernelRange.Start + dtbBase := dtbRange.Start + + var trampoline [10]uint32 + // Instruction encoding per + // "Arm Architecture Reference Manual Armv8, for Armv8-A architecture + // profile" [ ARM DDI 0487E.a (ID070919) ] + trampoline[0] = 0x580000c4 // ldr x4, #0x18 (PC relative: trampoline[6 and 7]) + trampoline[1] = 0x580000e0 // ldr x0, #0x1c (PC relative: trampoline[8 and 9]) + // Zero out x1, x2, x3 + trampoline[2] = 0xaa1f03e1 // mov x1, xzr + trampoline[3] = 0xaa1f03e2 // mov x2, xzr + trampoline[4] = 0xaa1f03e3 // mov x3, xzr + // Branch register / Jump to instruction from x4. + trampoline[5] = 0xd61f0080 // br x4 + + trampoline[6] = uint32(uint64(kernelEntry) & 0xffffffff) + trampoline[7] = uint32(uint64(kernelEntry) >> 32) + trampoline[8] = uint32(uint64(dtbBase) & 0xffffffff) + trampoline[9] = uint32(uint64(dtbBase) >> 32) + + var trampolineBuffer bytes.Buffer + if err := binary.Write(&trampolineBuffer, binary.LittleEndian, trampoline); err != nil { + return nil, fmt.Errorf("make trampoline: %v", err) + } + Debug("trampoline bytes %x", trampolineBuffer.Bytes()) + trampolineRange, err := kmem.AddKexecSegment(trampolineBuffer.Bytes()) + if err != nil { + return nil, fmt.Errorf("%w: %w", errTrampolineSegmentFailed, err) + } + Debug("Added %d byte trampoline at %s", len(trampolineBuffer.Bytes()), trampolineRange) + + /* Load it */ + img.entry = trampolineRange.Start + img.segments = kmem.Segments + Debug("Entry: %#x", img.entry) + return img, nil +} diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go new file mode 100644 index 0000000000..c6671405c5 --- /dev/null +++ b/pkg/boot/linux/load_linux_image_test.go @@ -0,0 +1,399 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linux + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "os" + "path/filepath" + "testing" + + "github.com/u-root/u-root/pkg/boot/kexec" + "github.com/u-root/u-root/pkg/dt" +) + +func readFile(t *testing.T, path string) []byte { + t.Helper() + b, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + return b +} + +func createFile(t *testing.T, content []byte) *os.File { + t.Helper() + p := filepath.Join(t.TempDir(), "file") + if err := os.WriteFile(p, content, 0o777); err != nil { + t.Fatal(err) + } + f, err := os.Open(p) + if err != nil { + t.Fatal(err) + } + return f +} + +func closedFile(t *testing.T) *os.File { + t.Helper() + f, err := os.Create(filepath.Join(t.TempDir(), "file")) + if err != nil { + t.Fatal(err) + } + f.Close() + return f +} + +func openFile(t *testing.T, path string) *os.File { + t.Helper() + f, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + return f +} + +func fdtBytes(t *testing.T, fdt *dt.FDT) []byte { + t.Helper() + var b bytes.Buffer + fdt.Header.Magic = dt.Magic + fdt.Header.Version = 17 + if _, err := fdt.Write(&b); err != nil { + t.Fatal(err) + } + return b.Bytes() +} + +func fdtReader(t *testing.T, fdt *dt.FDT) io.ReaderAt { + t.Helper() + var b bytes.Buffer + fdt.Header.Magic = dt.Magic + fdt.Header.Version = 17 + if _, err := fdt.Write(&b); err != nil { + t.Fatal(err) + } + return bytes.NewReader(b.Bytes()) +} + +func pipe(t *testing.T, content []byte) *os.File { + t.Helper() + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + go func() { + _, _ = io.Copy(w, bytes.NewReader(content)) + w.Close() + }() + return r +} + +func trampoline(kernelEntry, dtbBase uint64) []byte { + t := []byte{ + 0xc4, 0x00, 0x00, 0x58, + 0xe0, 0x00, 0x00, 0x58, + 0xe1, 0x03, 0x1f, 0xaa, + 0xe2, 0x03, 0x1f, 0xaa, + 0xe3, 0x03, 0x1f, 0xaa, + 0x80, 0x00, 0x1f, 0xd6, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + } + binary.LittleEndian.PutUint64(t[24:], kernelEntry) + binary.LittleEndian.PutUint64(t[32:], dtbBase) + return t +} + +func TestKexecLoadImage(t *testing.T) { + Debug = t.Logf + + for _, tt := range []struct { + name string + + // Inputs + kernel *os.File + ramfs *os.File + fdt io.ReaderAt + cmdline string + reservations kexec.Ranges + + // Results + segments kexec.Segments + entry uintptr + errs []error + }{ + { + name: "load-no-initramfs", + kernel: openFile(t, "../image/testdata/Image"), + entry: 0x101000, /* trampoline entry */ + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 500), + dt.PropertyU64("linux,initrd-end", 500), + dt.PropertyString("bootargs", "ohno"), + )), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), + segments: kexec.Segments{ + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x200000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), + }, + }, + { + name: "load-initramfs-and-cmdline", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + cmdline: "foobar", + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), + entry: 0x102000, + segments: kexec.Segments{ + kexec.NewSegment([]byte("ramfs"), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 0x100000), + // TODO: should this actually be 0x100005? + dt.PropertyU64("linux,initrd-end", 0x101000), + dt.PropertyString("bootargs", "foobar"), + )), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x200000, 0x101000), kexec.Range{Start: 0x102000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), + }, + }, + { + name: "pipefile", + kernel: pipe(t, readFile(t, "../image/testdata/Image")), + entry: 0x101000, + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 500), + dt.PropertyU64("linux,initrd-end", 500), + )), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), + segments: kexec.Segments{ + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x200000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), + }, + }, + { + name: "no chosen node in fdt", + kernel: openFile(t, "../image/testdata/Image"), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), + errs: []error{errNoChosenNode}, + }, + { + name: "not enough space for kernel image", + kernel: openFile(t, "../image/testdata/Image"), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0, 0x100000), + )), + ))}), + errs: []error{errKernelSegmentFailed, kexec.ErrNotEnoughSpace}, + }, + { + name: "kernel-error", + kernel: closedFile(t), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0, 0x1000000), + )), + ))}), + errs: []error{os.ErrClosed}, + }, + { + name: "initramfs-error", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: closedFile(t), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0, 0x1000000), + )), + ))}), + errs: []error{os.ErrClosed}, + }, + { + name: "not-enough-space-for-kernel-and-initramfs", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // kernel size is 0x940000, which rounds up to 0xa00000 + dt.PropertyRegion("reg", 0x200000, 0xa00000), + )), + ))}), + errs: []error{errInitramfsSegmentFailed, kexec.ErrNotEnoughSpace}, + }, + { + name: "not-enough-space-for-dtb", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // kernel size is 0x940000, which rounds up to 0xa00000 + // Initramfs takes another 0x1000 + dt.PropertyRegion("reg", 0x200000, 0xa01000), + )), + ))}), + errs: []error{errDTBSegmentFailed, kexec.ErrNotEnoughSpace}, + }, + { + name: "not-enough-space-for-trampoline", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // kernel size is 0x940000, which rounds up to 0xa00000 + // Initramfs takes another 0x1000 + // DTB takes another 0x1000 + dt.PropertyRegion("reg", 0x200000, 0xa02000), + )), + ))}), + errs: []error{errTrampolineSegmentFailed, kexec.ErrNotEnoughSpace}, + }, + { + name: "loadFDT fails", + fdt: closedFile(t), + errs: []error{os.ErrClosed}, + }, + { + name: "invalid-memmap", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // Too short. + dt.Property{Name: "reg", Value: []byte{0x0}}, + )), + ))}), + errs: []error{dt.ErrPropertyRegionInvalid}, + }, + { + name: "no-memmap", + kernel: openFile(t, "../image/testdata/Image"), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", + dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 500), + dt.PropertyU64("linux,initrd-end", 500), + )), + ), + )}), + errs: []error{ErrMemmapEmpty}, + }, + { + name: "load-with-reservation", + kernel: openFile(t, "../image/testdata/Image"), + entry: 0x101000, /* trampoline entry */ + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), + reservations: []kexec.Range{ + // Kernel would normally be allocated here. + // This forces kernel to go for the next 2M boundary, 0x400000. + {Start: 0x200000, Size: 0x1}, + }, + segments: kexec.Segments{ + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x400000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x400000, Size: 0xa00000}), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + got, err := kexecLoadImage(tt.kernel, tt.ramfs, tt.cmdline, tt.fdt, tt.reservations) + for _, wantErr := range tt.errs { + if !errors.Is(err, wantErr) { + t.Errorf("kexecLoad Arm Image = %v, want %v", err, wantErr) + } + } + if got == nil { + return + } + if got.entry != tt.entry { + t.Errorf("kexecLoad Arm Image = %#x, want %#x", got.entry, tt.entry) + } + if !kexec.SegmentsEqual(got.segments, tt.segments) { + t.Errorf("kexecLoad Arm Image =\n%v, want\n%v", got.segments, tt.segments) + } + for i := range got.segments { + if !kexec.SegmentEqual(got.segments[i], tt.segments[i]) { + t.Errorf("Segment %d wrong", i) + } + } + }) + } +} diff --git a/pkg/boot/linux/load_other_linux.go b/pkg/boot/linux/load_other_linux.go index de24661567..ee71973999 100644 --- a/pkg/boot/linux/load_other_linux.go +++ b/pkg/boot/linux/load_other_linux.go @@ -8,12 +8,14 @@ package linux import ( + "io" "os" + "github.com/u-root/u-root/pkg/boot/kexec" "golang.org/x/sys/unix" ) // KexecLoad is not implemented for platforms other than amd64 and arm64. -func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservations kexec.Ranges) error { return unix.ENOSYS } diff --git a/pkg/boot/linux/opts.go b/pkg/boot/linux/opts.go deleted file mode 100644 index d2ddcde279..0000000000 --- a/pkg/boot/linux/opts.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2022 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package linux - -import ( - "encoding/json" - "io" -) - -// KexecOptions abstract a collection of options to be passed in KexecLoad. -// -// Arch agnostic. Each arch knows to just look for options they care about. -// Alternatively, we could introduce arch specific options, so irrelevant options -// won't be compiled. But for simplification, have one shared struct to begin -// with, we can split when time comes. -type KexecOptions struct { - // DTB is used as the device tree blob, if specified. - DTB io.ReaderAt - - // Mmap kernel and initramfs, so virtual pages are directly mapped - // to page cache. Here it is agnostic to whether original kernel and - // initramfs file is in tmpfs, or other devices. - // - // *) If in tmpfs, file objects are backed by page cache. - // *) If on disk, kernel cache it during first disk I/O. In case when - // we are mmapping a file on disk, pages frames backing the page cache - // are only allocated when bytes are accessed, as kernel is lazy, so disk - // I/O happens at kexec load time. - // - // MmapKernel indicates if mmap kernel kernel into virtual memory. - MmapKernel bool - // MmapRamfs indicates if mmap initramfs into virtual memory. - MmapRamfs bool -} - -// kexecOptionsJSON is same as KexecOptions, but with transformed fields to help with serialization of KexecOptions. -type kexecOptionsJSON struct { - dtb string - mmapKernel bool - mmapRamfs bool -} - -func (ko *KexecOptions) MarshalJSON() ([]byte, error) { - koJSON := kexecOptionsJSON{} - // TODO(100000TB): consider support serializing dtb. - // - // We can either change default type to path name, or - // read it and save it to a file under tmpfs during - // marshaling. - - koJSON.mmapKernel = ko.MmapKernel - koJSON.mmapRamfs = ko.MmapRamfs - return json.Marshal(&koJSON) -} - -func (ko *KexecOptions) UnmarshalJSON(b []byte) error { - koJSON := kexecOptionsJSON{} - if err := json.Unmarshal(b, &koJSON); err != nil { - return err - } - ko.MmapKernel = koJSON.mmapKernel - ko.MmapRamfs = koJSON.mmapRamfs - return nil -} diff --git a/pkg/boot/linux/pkg_test.go b/pkg/boot/linux/pkg_test.go deleted file mode 100644 index 54803001d1..0000000000 --- a/pkg/boot/linux/pkg_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2022 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package linux - -import ( - "testing" -) - -// Empty test so that Go coverage detects 0% for this package. -func TestEmpty(t *testing.T) { - t.Logf("TODO") -} diff --git a/pkg/boot/linux_test.go b/pkg/boot/linux_test.go index 83a8e70c90..ebb923ff0b 100644 --- a/pkg/boot/linux_test.go +++ b/pkg/boot/linux_test.go @@ -6,6 +6,7 @@ package boot import ( "bytes" + "errors" "fmt" "io" "net/url" @@ -15,11 +16,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/u-root/u-root/pkg/boot/linux" "github.com/u-root/u-root/pkg/curl" "github.com/u-root/u-root/pkg/mount" - "github.com/u-root/u-root/pkg/uio" - "github.com/u-root/u-root/pkg/vfile" + "github.com/u-root/uio/uio" "github.com/u-root/uio/ulog/ulogtest" "golang.org/x/sys/unix" ) @@ -87,45 +86,22 @@ func TestLinuxLabel(t *testing.T) { }, want: "Linux(kernel=http://127.0.0.1/kernel initrd=http://127.0.0.1/initrd1,http://127.0.0.1/initrd2)", }, - { - desc: "verified file", - img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, - Initrd: CatInitrds( - &vfile.File{Reader: nil, FileName: "/boot/initrd1"}, - &vfile.File{Reader: nil, FileName: "/boot/initrd2"}, - ), - }, - want: "Linux(kernel=/boot/foobar initrd=/boot/initrd1,/boot/initrd2)", - }, - { - desc: "no initrd", - img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, - Initrd: nil, - }, - want: "Linux(kernel=/boot/foobar)", - }, { desc: "dtb file", img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, - Initrd: &vfile.File{Reader: nil, FileName: "/boot/initrd"}, - KexecOpts: linux.KexecOptions{ - DTB: &vfile.File{Reader: nil, FileName: "/boot/board.dtb"}, - }, + Kernel: osKernel, + Initrd: osInitrd, + DTB: osInitrd, }, - want: "Linux(kernel=/boot/foobar initrd=/boot/initrd dtb=/boot/board.dtb)", + want: fmt.Sprintf("Linux(kernel=%s/kernel initrd=%s/initrd dtb=%s/initrd)", dir, dir, dir), }, { desc: "dtb file, no initrd", img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, - KexecOpts: linux.KexecOptions{ - DTB: &vfile.File{Reader: nil, FileName: "/boot/board.dtb"}, - }, + Kernel: osKernel, + DTB: osInitrd, }, - want: "Linux(kernel=/boot/foobar dtb=/boot/board.dtb)", + want: fmt.Sprintf("Linux(kernel=%s/kernel dtb=%s/initrd)", dir, dir), }, } { t.Run(tt.desc, func(t *testing.T) { @@ -187,20 +163,10 @@ func checkFilePath(t *testing.T, fsrc io.ReaderAt, fdst *os.File) { func setupTestFile(t *testing.T, path, content string) *os.File { t.Helper() - f, err := os.Create(path) - if err != nil { + if err := os.WriteFile(path, []byte(content), 0o777); err != nil { t.Fatal(err) } - n, err := f.Write([]byte(content)) - if err != nil { - t.Fatal(err) - } - if n != len([]byte(content)) { - t.Fatalf("want %d bytes written, but got %d", len([]byte(content)), n) - } - if err := f.Close(); err != nil { - t.Fatalf("could not close test file: %v", err) - } + nf, err := os.Open(path) if err != nil { t.Fatalf("could not open test file: %v", err) @@ -222,41 +188,28 @@ func GenerateCatDummyInitrd(t *testing.T, initrds ...string) string { return string(d) } -type wantData struct { - loadedImage *LoadedLinuxImage - cleanup func() - err error -} - func TestLoadLinuxImage(t *testing.T) { testDir := t.TempDir() for _, tt := range []struct { - name string - li *LinuxImage - want wantData + name string + li *LinuxImage + wantKernel *os.File + wantInitrd *os.File + err error }{ { - name: "kernel is nil", - li: &LinuxImage{Kernel: nil}, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: nil, - }, - err: errNilKernel, - }, + name: "kernel is nil", + li: &LinuxImage{Kernel: nil}, + wantKernel: nil, + err: errNilKernel, }, { name: "basic happy case w/o initrd", li: &LinuxImage{ Kernel: strings.NewReader("testkernel"), }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_wo_initrd_bzimage"), "testkernel"), - }, - err: nil, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_wo_initrd_bzimage"), "testkernel"), }, { name: "basic happy case w/ initrd", @@ -264,47 +217,28 @@ func TestLoadLinuxImage(t *testing.T) { Kernel: strings.NewReader("testkernel"), Initrd: strings.NewReader("testinitrd"), }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_initramfs"), "testinitrd"), - }, - err: nil, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_initramfs"), "testinitrd"), }, { name: "empty initrd, with DTB present", // Expect DTB appended to loaded initrd. li: &LinuxImage{ Kernel: strings.NewReader("testkernel"), Initrd: nil, - KexecOpts: linux.KexecOptions{ - DTB: strings.NewReader("testdtb"), - }, - }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_initramfs"), "testdtb"), - }, - err: nil, + DTB: strings.NewReader("testdtb"), }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_initramfs"), "testdtb"), }, { name: "non-empty initrd, with DTB present", // Expect DTB appended to loaded initrd. li: &LinuxImage{ Kernel: strings.NewReader("testkernel"), Initrd: strings.NewReader("testinitrd"), - KexecOpts: linux.KexecOptions{ - DTB: strings.NewReader("testdtb"), - }, - }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_initramfs"), GenerateCatDummyInitrd(t, "testinitrd", "testdtb")), - }, - err: nil, + DTB: strings.NewReader("testdtb"), }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_initramfs"), GenerateCatDummyInitrd(t, "testinitrd", "testdtb")), }, { name: "oringnal kernel and initrd are files, skip copying", @@ -312,31 +246,26 @@ func TestLoadLinuxImage(t *testing.T) { Kernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_bzImage"), "testkernel"), Initrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_initramfs"), "testinitrd"), }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_initramfs"), "testinitrd"), - }, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_initramfs"), "testinitrd"), }, } { t.Run(tt.name, func(t *testing.T) { - gotImage, _, gotErr := loadLinuxImage(tt.li, ulogtest.Logger{t}, true) - if gotErr != nil { - if gotErr != tt.want.err { - t.Errorf("got error %v, want %v", gotErr, tt.want.err) - } + gotKernel, gotInitrd, err := tt.li.loadImage(&loadOptions{logger: ulogtest.Logger{t}, verbose: true}) + if !errors.Is(err, tt.err) { + t.Errorf("got error %v, want %v", err, tt.err) + } else if err != nil { return } // Kernel is opened as read only, and contents match that from original LinuxImage. - checkReadOnly(t, gotImage.Kernel) + checkReadOnly(t, gotKernel) // If src is a read-only *os.File on tmpfs, shoukd skip copying. - checkFilePath(t, tt.li.Kernel, gotImage.Kernel) - kernelBytes, err := io.ReadAll(gotImage.Kernel) + checkFilePath(t, tt.li.Kernel, gotKernel) + kernelBytes, err := io.ReadAll(gotKernel) if err != nil { t.Fatalf("could not read kernel from loaded image: %v", err) } - wantBytes, err := io.ReadAll(tt.want.loadedImage.Kernel) + wantBytes, err := io.ReadAll(tt.wantKernel) if err != nil { t.Fatalf("could not read expected kernel: %v", err) } @@ -346,14 +275,14 @@ func TestLoadLinuxImage(t *testing.T) { // Initrd, if present, is opened as read only, and contents match that from original LinuxImage. // OR original initrd, with DTB appended. if tt.li.Initrd != nil { - checkReadOnly(t, gotImage.Initrd) + checkReadOnly(t, gotInitrd) // If src is a read-only *os.File on tmpfs, should skip copying. - checkFilePath(t, tt.li.Initrd, gotImage.Initrd) - initrdBytes, err := io.ReadAll(gotImage.Initrd) + checkFilePath(t, tt.li.Initrd, gotInitrd) + initrdBytes, err := io.ReadAll(gotInitrd) if err != nil { t.Fatalf("could not read initrd from loaded image: %v", err) } - wantInitrdBytes, err := io.ReadAll(tt.want.loadedImage.Initrd) + wantInitrdBytes, err := io.ReadAll(tt.wantInitrd) if err != nil { t.Fatalf("could not read expected initrd: %v", err) } diff --git a/pkg/boot/menu/menu.go b/pkg/boot/menu/menu.go index 32d12bca70..f897b55e12 100644 --- a/pkg/boot/menu/menu.go +++ b/pkg/boot/menu/menu.go @@ -267,14 +267,13 @@ func OSImages(verbose bool, imgs ...boot.OSImage) []Entry { // OSImageAction is a menu.Entry that boots an OSImage. type OSImageAction struct { boot.OSImage - Verbose bool - NoKexecLoad bool - LinuxImageCfgFile string + Verbose bool + NoKexecLoad bool } // Load implements Entry.Load by loading the OS image into memory. func (oia OSImageAction) Load() error { - if err := oia.OSImage.Load(boot.WithVerbose(oia.Verbose), boot.WithDryRun(oia.NoKexecLoad), boot.WithLinuxImageCfgFile(oia.LinuxImageCfgFile)); err != nil { + if err := oia.OSImage.Load(boot.WithVerbose(oia.Verbose), boot.WithDryRun(oia.NoKexecLoad)); err != nil { return fmt.Errorf("could not load image %s: %v", oia.OSImage, err) } return nil diff --git a/pkg/boot/multiboot/description.go b/pkg/boot/multiboot/description.go index fbf3120b2b..a784b2699a 100644 --- a/pkg/boot/multiboot/description.go +++ b/pkg/boot/multiboot/description.go @@ -11,7 +11,7 @@ import ( "fmt" "strconv" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // DebugPrefix is a prefix that some messages are printed with for tests to parse. diff --git a/pkg/boot/multiboot/module.go b/pkg/boot/multiboot/module.go index 76bf9006ef..abc84ee5a9 100644 --- a/pkg/boot/multiboot/module.go +++ b/pkg/boot/multiboot/module.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/align" "github.com/u-root/u-root/pkg/ubinary" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // A module represents a module to be loaded along with the kernel. diff --git a/pkg/boot/multiboot/multiboot.go b/pkg/boot/multiboot/multiboot.go index 9d3e131101..d6d8e42c96 100644 --- a/pkg/boot/multiboot/multiboot.go +++ b/pkg/boot/multiboot/multiboot.go @@ -25,7 +25,7 @@ import ( "github.com/u-root/u-root/pkg/boot/multiboot/internal/trampoline" "github.com/u-root/u-root/pkg/boot/util" "github.com/u-root/u-root/pkg/ubinary" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const bootloader = "u-root kexec" @@ -290,9 +290,11 @@ func (m *multiboot) load(debug bool, ibft *ibft.IBFT) error { } log.Printf("Parsing memory map") - if err := m.mem.ParseMemoryMap(); err != nil { + memmap, err := kexec.MemoryMapFromSysfsMemmap() + if err != nil { return fmt.Errorf("error parsing memory map: %v", err) } + m.mem.Phys = memmap // Insert the iBFT now, since nothing else has been allocated and this // is the most restricted allocation we're gonna have to make. diff --git a/pkg/boot/multiboot/mutiboot_info.go b/pkg/boot/multiboot/mutiboot_info.go index f00c784fcf..a6e0502f33 100644 --- a/pkg/boot/multiboot/mutiboot_info.go +++ b/pkg/boot/multiboot/mutiboot_info.go @@ -5,7 +5,7 @@ package multiboot import ( - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type esxBootInfoInfo struct { diff --git a/pkg/boot/netboot/ipxe/ipxe.go b/pkg/boot/netboot/ipxe/ipxe.go index c0945d71ec..cb6eb7e850 100644 --- a/pkg/boot/netboot/ipxe/ipxe.go +++ b/pkg/boot/netboot/ipxe/ipxe.go @@ -17,8 +17,8 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/ulog" + "github.com/u-root/uio/uio" ) // ErrNotIpxeScript is returned when the config file is not an diff --git a/pkg/boot/netboot/ipxe/ipxe_test.go b/pkg/boot/netboot/ipxe/ipxe_test.go index 11d748a83d..82d85d6904 100644 --- a/pkg/boot/netboot/ipxe/ipxe_test.go +++ b/pkg/boot/netboot/ipxe/ipxe_test.go @@ -15,8 +15,8 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/ulog/ulogtest" + "github.com/u-root/uio/uio" ) func mustReadAll(r io.ReaderAt) string { diff --git a/pkg/boot/syslinux/syslinux.go b/pkg/boot/syslinux/syslinux.go index 4a7205060a..c86d634a4b 100644 --- a/pkg/boot/syslinux/syslinux.go +++ b/pkg/boot/syslinux/syslinux.go @@ -24,7 +24,7 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func probeIsolinuxFiles() []string { @@ -393,7 +393,7 @@ func (c *parser) append(ctx context.Context, config string) error { if err != nil { return err } - e.KexecOpts.DTB = dtb + e.DTB = dtb } case "append": diff --git a/pkg/boot/syslinux/syslinux_test.go b/pkg/boot/syslinux/syslinux_test.go index b43651b3b8..46cd14fc14 100644 --- a/pkg/boot/syslinux/syslinux_test.go +++ b/pkg/boot/syslinux/syslinux_test.go @@ -19,7 +19,6 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/boot/boottest" - "github.com/u-root/u-root/pkg/boot/linux" "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/curl" ) @@ -557,11 +556,10 @@ func TestParseGeneral(t *testing.T) { }, want: []boot.OSImage{ &boot.LinuxImage{ - Name: "foo", - Kernel: strings.NewReader(kernel1), - Initrd: strings.NewReader(globalInitrd), - Cmdline: "foo=bar", - KexecOpts: linux.KexecOptions{}, + Name: "foo", + Kernel: strings.NewReader(kernel1), + Initrd: strings.NewReader(globalInitrd), + Cmdline: "foo=bar", }, }, }, diff --git a/pkg/boot/uefi/uefi.go b/pkg/boot/uefi/uefi.go index 29f9f06fe5..aeb98db6d6 100644 --- a/pkg/boot/uefi/uefi.go +++ b/pkg/boot/uefi/uefi.go @@ -20,10 +20,10 @@ import ( ) var ( - kexecLoad = kexec.Load - kexecParseMemoryMap = kexec.ParseMemoryMap - getRSDP = acpi.GetRSDP - getSMBIOSBase = smbios.SMBIOSBase + kexecLoad = kexec.Load + kexecMemoryMapFromSysfsMemmap = kexec.MemoryMapFromSysfsMemmap + getRSDP = acpi.GetRSDP + getSMBIOSBase = smbios.SMBIOSBase ) // SerialPortConfig defines debug port configuration @@ -123,7 +123,7 @@ func (fv *FVImage) Load(verbose bool) error { configAddr := fv.ImageBase - uintptr(uefiPayloadConfigSize) // Get MemoryMap - mm, err := kexecParseMemoryMap() + mm, err := kexecMemoryMapFromSysfsMemmap() if err != nil { return err } @@ -154,7 +154,7 @@ func (fv *FVImage) Load(verbose bool) error { return err } - if err := binary.Write(pcbuf, binary.LittleEndian, mm.AsPayloadParam()); err != nil { + if err := binary.Write(pcbuf, binary.LittleEndian, mm.ToUEFIPayloadMemoryMap()); err != nil { return err } diff --git a/pkg/boot/uefi/uefi_test.go b/pkg/boot/uefi/uefi_test.go index f51b779e55..0d74a23634 100644 --- a/pkg/boot/uefi/uefi_test.go +++ b/pkg/boot/uefi/uefi_test.go @@ -32,7 +32,7 @@ func mockKexecLoad(entry uintptr, segments kexec.Segments, flags uint64) error { return nil } -func mockKexecParseMemoryMap() (kexec.MemoryMap, error) { +func mockKexecMemoryMapFromSysfsMemmap() (kexec.MemoryMap, error) { return kexec.MemoryMap{}, nil } @@ -50,8 +50,8 @@ func TestLoadFvImage(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP @@ -97,14 +97,14 @@ func TestLoadFvImageNotFound(t *testing.T) { } } -func TestLoadFvImageFailAtParseMemoryMap(t *testing.T) { +func TestLoadFvImageFailAtMemoryMapFromSysfsMemmap(t *testing.T) { fv, err := New("testdata/fv_with_sec.fd") if err != nil { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = func() (kexec.MemoryMap, error) { + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = func() (kexec.MemoryMap, error) { return nil, fmt.Errorf("PARSE_MEMORY_MAP_FAILED") } @@ -122,8 +122,8 @@ func TestLoadFvImageFailAtGetRSDP(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = func() (*acpi.RSDP, error) { @@ -144,8 +144,8 @@ func TestLoadFvImageFailAtGetSMBIOS(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP @@ -170,8 +170,8 @@ func TestLoadFvImageFailAtKexec(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP diff --git a/pkg/boot/util/reader.go b/pkg/boot/util/reader.go index 28f608c2e8..21a751ec28 100644 --- a/pkg/boot/util/reader.go +++ b/pkg/boot/util/reader.go @@ -9,7 +9,7 @@ import ( "compress/gzip" "io" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func readGzip(r io.Reader) ([]byte, error) { diff --git a/pkg/cpio/fs_plan9.go b/pkg/cpio/fs_plan9.go index e976e2a40e..5abc07bf6e 100644 --- a/pkg/cpio/fs_plan9.go +++ b/pkg/cpio/fs_plan9.go @@ -14,8 +14,8 @@ import ( "time" "github.com/u-root/u-root/pkg/ls" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/upath" + "github.com/u-root/uio/uio" ) // A Recorder is a structure that contains variables used to calculate diff --git a/pkg/cpio/fs_unix.go b/pkg/cpio/fs_unix.go index 29d1f242a3..d048fd9b8e 100644 --- a/pkg/cpio/fs_unix.go +++ b/pkg/cpio/fs_unix.go @@ -17,8 +17,8 @@ import ( "time" "github.com/u-root/u-root/pkg/ls" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/upath" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) @@ -124,7 +124,7 @@ func CreateFileInRoot(f Record, rootDir string, forcePriv bool) error { // mode 755. if _, err := os.Stat(dir); os.IsNotExist(err) && len(dir) > 0 { if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("CreateFileInRoot %q: %v", f.Name, err) + return fmt.Errorf("CreateFileInRoot %q: %w", f.Name, err) } } diff --git a/pkg/cpio/fs_windows.go b/pkg/cpio/fs_windows.go index bdaf3d99f2..cc2079166f 100644 --- a/pkg/cpio/fs_windows.go +++ b/pkg/cpio/fs_windows.go @@ -14,8 +14,8 @@ import ( "time" "github.com/u-root/u-root/pkg/ls" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/upath" + "github.com/u-root/uio/uio" ) // A Recorder is a structure that contains variables used to calculate @@ -86,7 +86,7 @@ func CreateFileInRoot(f Record, rootDir string, forcePriv bool) error { // mode 755. if _, err := os.Stat(dir); os.IsNotExist(err) && len(dir) > 0 { if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("CreateFileInRoot %q: %v", f.Name, err) + return fmt.Errorf("CreateFileInRoot %q: %w", f.Name, err) } } diff --git a/pkg/cpio/newc.go b/pkg/cpio/newc.go index 44e616e559..ff72ce5a55 100644 --- a/pkg/cpio/newc.go +++ b/pkg/cpio/newc.go @@ -12,7 +12,7 @@ import ( "io" "os" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const ( @@ -259,7 +259,7 @@ func (r *reader) read(p []byte) error { } if err != nil || n != len(p) { - return fmt.Errorf("ReadAt(pos = %d): got %d, want %d bytes; error %v", r.pos, n, len(p), err) + return fmt.Errorf("ReadAt(pos = %d): got %d, want %d bytes; error %w", r.pos, n, len(p), err) } r.pos += int64(n) @@ -290,7 +290,7 @@ func (r *reader) ReadRecord() (Record, error) { // Decode hex header fields. dst := make([]byte, binary.Size(hdr)) if _, err := hex.Decode(dst, buf[magicLen:]); err != nil { - return Record{}, fmt.Errorf("reader: error decoding hex: %v", err) + return Record{}, fmt.Errorf("reader: error decoding hex: %w", err) } if err := binary.Read(bytes.NewReader(dst), binary.BigEndian, &hdr); err != nil { return Record{}, err diff --git a/pkg/cpio/newc_test.go b/pkg/cpio/newc_test.go index b8a600f9d8..88fbf9eee8 100644 --- a/pkg/cpio/newc_test.go +++ b/pkg/cpio/newc_test.go @@ -14,7 +14,7 @@ import ( "syscall" "testing" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) /* diff --git a/pkg/cpio/utils.go b/pkg/cpio/utils.go index 988c1a5f16..b4c507395b 100644 --- a/pkg/cpio/utils.go +++ b/pkg/cpio/utils.go @@ -12,7 +12,7 @@ import ( "path" "strings" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // Trailer is the name of the trailer record. @@ -131,7 +131,7 @@ func (dw *DedupWriter) WriteRecord(rec Record) error { func WriteRecords(w RecordWriter, files []Record) error { for _, f := range files { if err := w.WriteRecord(f); err != nil { - return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err) + return fmt.Errorf("WriteRecords: writing %q got %w", f.Info.Name, err) } } return nil @@ -190,7 +190,7 @@ func WriteRecordsAndDirs(rw RecordWriter, files []Record) error { } recs = append(recs, f) if err := WriteRecords(rw, recs); err != nil { - return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err) + return fmt.Errorf("WriteRecords: writing %q got %w", f.Info.Name, err) } } return nil diff --git a/pkg/curl/schemes.go b/pkg/curl/schemes.go index 75cd531212..8bac3bc0da 100644 --- a/pkg/curl/schemes.go +++ b/pkg/curl/schemes.go @@ -22,7 +22,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "pack.ag/tftp" ) diff --git a/pkg/curl/schemes_test.go b/pkg/curl/schemes_test.go index 42d61041f2..f9b075ba5b 100644 --- a/pkg/curl/schemes_test.go +++ b/pkg/curl/schemes_test.go @@ -15,7 +15,7 @@ import ( "testing" "github.com/cenkalti/backoff/v4" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) var ( diff --git a/pkg/dhclient/dhclient.go b/pkg/dhclient/dhclient.go index 33170b9b68..fe18c01d90 100644 --- a/pkg/dhclient/dhclient.go +++ b/pkg/dhclient/dhclient.go @@ -27,9 +27,9 @@ import ( "golang.org/x/sys/unix" ) -// isIpv6LinkReady returns true if the interface has a link-local address +// isIPv6LinkReady returns true if the interface has a link-local address // which is not tentative. -func isIpv6LinkReady(l netlink.Link) (bool, error) { +func isIPv6LinkReady(l netlink.Link) (bool, error) { addrs, err := netlink.AddrList(l, netlink.FAMILY_V6) if err != nil { return false, err @@ -45,6 +45,31 @@ func isIpv6LinkReady(l netlink.Link) (bool, error) { return false, nil } +// isIPv6RouteReady returns true if serverAddr is reachable. +func isIPv6RouteReady(l netlink.Link, serverAddr net.IP) (bool, error) { + if serverAddr.IsMulticast() { + return true, nil + } + + routes, err := netlink.RouteList(l, netlink.FAMILY_V6) + if err != nil { + return false, err + } + for _, route := range routes { + if route.LinkIndex != l.Attrs().Index { + continue + } + // Default route. + if route.Dst == nil { + return true, nil + } + if route.Dst.Contains(serverAddr) { + return true, nil + } + } + return false, nil +} + // IfUp ensures the given network interface is up and returns the link object. func IfUp(ifname string, linkUpTimeout time.Duration) (netlink.Link, error) { start := time.Now() @@ -54,7 +79,7 @@ func IfUp(ifname string, linkUpTimeout time.Duration) (netlink.Link, error) { // ethernet can just vanish. iface, err := netlink.LinkByName(ifname) if err != nil { - return nil, fmt.Errorf("cannot get interface %q by name: %v", ifname, err) + return nil, fmt.Errorf("cannot get interface %q by name: %w", ifname, err) } // Check if link is actually operational. @@ -65,7 +90,7 @@ func IfUp(ifname string, linkUpTimeout time.Duration) (netlink.Link, error) { } if err := netlink.LinkSetUp(iface); err != nil { - return nil, fmt.Errorf("interface %q: %v can't make it up: %v", ifname, iface, err) + return nil, fmt.Errorf("interface %q: %v can't make it up: %w", ifname, iface, err) } time.Sleep(100 * time.Millisecond) } @@ -230,7 +255,7 @@ func lease6(ctx context.Context, iface netlink.Link, c Config, linkUpTimeout tim // Hardcode the timeout to 30s for now. linkTimeout := time.After(linkUpTimeout) for { - if ready, err := isIpv6LinkReady(iface); err != nil { + if ready, err := isIPv6LinkReady(iface); err != nil { return nil, err } else if ready { break @@ -239,12 +264,29 @@ func lease6(ctx context.Context, iface netlink.Link, c Config, linkUpTimeout tim case <-time.After(100 * time.Millisecond): continue case <-linkTimeout: - return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") case <-ctx.Done(): return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") } } + // If user specified a non-multicast address, make sure it's routable before we start. + if c.V6ServerAddr != nil { + for { + if ready, err := isIPv6RouteReady(iface, c.V6ServerAddr.IP); err != nil { + return nil, err + } else if ready { + break + } + select { + case <-time.After(100 * time.Millisecond): + continue + case <-linkTimeout: + case <-ctx.Done(): + return nil, errors.New("timeout after waiting for a route") + } + } + } + mods := []nclient6.ClientOpt{ nclient6.WithTimeout(c.Timeout), nclient6.WithRetry(c.Retries), diff --git a/pkg/dhclient/dhcp4.go b/pkg/dhclient/dhcp4.go index de4c76e5fa..e0fb70f1ab 100644 --- a/pkg/dhclient/dhcp4.go +++ b/pkg/dhclient/dhcp4.go @@ -78,7 +78,7 @@ func (p *Packet4) Configure() error { IPNet: l, } if err := netlink.AddrReplace(p.iface, dst); err != nil { - return fmt.Errorf("add/replace %s to %v: %v", dst, p.iface, err) + return fmt.Errorf("add/replace %s to %v: %w", dst, p.iface, err) } // RFC 3442 notes that if classless static routes are available, they @@ -96,7 +96,7 @@ func (p *Packet4) Configure() error { } if err := netlink.RouteReplace(r); err != nil { - return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) + return fmt.Errorf("%s: add %s: %w", p.iface.Attrs().Name, r, err) } } } else if gw := p.P.Router(); len(gw) > 0 { @@ -106,7 +106,7 @@ func (p *Packet4) Configure() error { } if err := netlink.RouteReplace(r); err != nil { - return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) + return fmt.Errorf("%s: add %s: %w", p.iface.Attrs().Name, r, err) } } diff --git a/pkg/dhclient/dhcp6.go b/pkg/dhclient/dhcp6.go index 9c950fd2f5..a8b6d7aa11 100644 --- a/pkg/dhclient/dhcp6.go +++ b/pkg/dhclient/dhcp6.go @@ -79,7 +79,7 @@ func (p *Packet6) Configure() error { if err := netlink.AddrReplace(p.iface, dst); err != nil { if os.IsExist(err) { - return fmt.Errorf("add/replace %s to %v: %v", dst, p.iface, err) + return fmt.Errorf("add/replace %s to %v: %w", dst, p.iface, err) } } diff --git a/pkg/dhclient/iface.go b/pkg/dhclient/iface.go index e9dd5f9c55..c39f991217 100644 --- a/pkg/dhclient/iface.go +++ b/pkg/dhclient/iface.go @@ -41,7 +41,7 @@ func Interfaces(ifName string) ([]netlink.Link, error) { ifnames, err := netlink.LinkList() if err != nil { - return nil, fmt.Errorf("can not get list of link names: %v", err) + return nil, fmt.Errorf("can not get list of link names: %w", err) } var filteredIfs []netlink.Link diff --git a/pkg/dhclient/iscsiuri.go b/pkg/dhclient/iscsiuri.go index f0f977f740..bbecc7dceb 100644 --- a/pkg/dhclient/iscsiuri.go +++ b/pkg/dhclient/iscsiuri.go @@ -94,7 +94,7 @@ func ParseISCSIURI(s string) (*net.TCPAddr, string, error) { if len(tok) > 0 { pv, err := strconv.Atoi(tok) if err != nil { - return nil, "", fmt.Errorf("iSCSI URI %q has invalid port: %v", s, err) + return nil, "", fmt.Errorf("iSCSI URI %q has invalid port: %w", s, err) } port = pv } @@ -103,7 +103,7 @@ func ParseISCSIURI(s string) (*net.TCPAddr, string, error) { } } if i.err != nil { - return nil, "", fmt.Errorf("iSCSI URI %q failed to parse: %v", s, i.err) + return nil, "", fmt.Errorf("iSCSI URI %q failed to parse: %w", s, i.err) } if magic != "iscsi" { return nil, "", fmt.Errorf("iSCSI URI %q is missing iscsi scheme prefix, have %s", s, magic) diff --git a/pkg/dt/fdt.go b/pkg/dt/fdt.go index 9947a81c2b..09fcb382b5 100644 --- a/pkg/dt/fdt.go +++ b/pkg/dt/fdt.go @@ -17,7 +17,7 @@ import ( "unsafe" "github.com/u-root/u-root/pkg/align" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const ( @@ -78,20 +78,20 @@ type ReserveEntry struct { func ReadFDT(f io.ReadSeeker) (*FDT, error) { fdt := &FDT{} if err := fdt.readHeader(f); err != nil { - return nil, err + return nil, fmt.Errorf("reading FDT header: %w", err) } if err := fdt.readMemoryReservationBlock(f); err != nil { - return nil, err + return nil, fmt.Errorf("reading memory reservation block: %w", err) } if err := fdt.checkLayout(); err != nil { - return nil, err + return nil, fmt.Errorf("layout check: %w", err) } strs, err := fdt.readStringsBlock(f) if err != nil { - return nil, err + return nil, fmt.Errorf("reading strings block: %w", err) } if err := fdt.readStructBlock(f, strs); err != nil { - return nil, err + return nil, fmt.Errorf("reading struct block: %w", err) } return fdt, nil } diff --git a/pkg/dt/node.go b/pkg/dt/node.go index e7bd10d380..bd342c1ec7 100644 --- a/pkg/dt/node.go +++ b/pkg/dt/node.go @@ -50,7 +50,7 @@ var StandardPropertyTypes = map[string]PropertyType{ var ( errInvalidChildIndex = errors.New("invalid child index") - errPropertyRegionInvalid = errors.New("property value is not ") + ErrPropertyRegionInvalid = errors.New("property value is not ") ) // Node is one Node in the Device Tree. @@ -60,6 +60,34 @@ type Node struct { Children []*Node `json:",omitempty"` } +// NodeOptioner is used to modify a Node for NewNode. +type NodeOptioner func(n *Node) + +// WithProperty adds the given properties to the node. +func WithProperty(p ...Property) NodeOptioner { + return func(n *Node) { + n.Properties = append(n.Properties, p...) + } +} + +// WithChildren adds childen to the node. +func WithChildren(c ...*Node) NodeOptioner { + return func(n *Node) { + n.Children = append(n.Children, c...) + } +} + +// NewNode creates a node. +func NewNode(name string, opts ...NodeOptioner) *Node { + n := &Node{ + Name: name, + } + for _, opt := range opts { + opt(n) + } + return n +} + // FindFirstMatchingChildIndex returns the index of the first immediate child node satisfying the // given predicate. func (n *Node) FindFirstMatchingChildIndex(predicate func(*Node) bool) (int, bool) { @@ -184,6 +212,47 @@ func (n *Node) UpdateProperty(name string, value []byte) bool { return false } +// Update updates an existing property with the given one, if they match in +// name, or adds a new property. +func (n *Node) Update(prop Property) bool { + p, found := n.LookProperty(prop.Name) + if found { + p.Value = prop.Value + return true + } + n.Properties = append(n.Properties, prop) + return false +} + +// PropertyU64 creates a uint64 property. +func PropertyU64(name string, value uint64) Property { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, value) + return Property{ + Name: name, + Value: b, + } +} + +// PropertyString creates a string property. +func PropertyString(name string, value string) Property { + return Property{ + Name: name, + Value: append([]byte(value), 0), + } +} + +// PropertyRegion creates a property encoding start and size of a region of memory. +func PropertyRegion(name string, start, size uint64) Property { + b := bytes.NewBuffer(nil) + _ = binary.Write(b, binary.BigEndian, start) + _ = binary.Write(b, binary.BigEndian, size) + return Property{ + Name: name, + Value: b.Bytes(), + } +} + // Property is a name-value pair. Note the PropertyType of Value is not // encoded. type Property struct { @@ -288,7 +357,7 @@ type Region struct { // AsRegion converts the property to a Region. func (p *Property) AsRegion() (*Region, error) { if len(p.Value) != 16 { - return nil, errPropertyRegionInvalid + return nil, ErrPropertyRegionInvalid } var start, size uint64 b := bytes.NewBuffer(p.Value) diff --git a/pkg/dt/node_test.go b/pkg/dt/node_test.go index 29e303a2f3..5726b3ea0f 100644 --- a/pkg/dt/node_test.go +++ b/pkg/dt/node_test.go @@ -69,6 +69,58 @@ func TestLookupImmediateChild(t *testing.T) { } } +func TestNewNode(t *testing.T) { + for _, tt := range []struct { + name string + opts []NodeOptioner + node *Node + }{ + { + name: "new", + opts: nil, + node: &Node{Name: "new"}, + }, + { + name: "new-with-property", + opts: []NodeOptioner{WithProperty(PropertyU64("foo", 1))}, + node: &Node{ + Name: "new-with-property", + Properties: []Property{ + { + Name: "foo", + Value: []byte{0, 0, 0, 0, 0, 0, 0, 1}, + }, + }, + }, + }, + { + name: "new-with-children", + opts: []NodeOptioner{WithChildren(NewNode("child", WithProperty(PropertyString("foo", "abc"))))}, + node: &Node{ + Name: "new-with-children", + Children: []*Node{ + { + Name: "child", + Properties: []Property{ + { + Name: "foo", + Value: []byte{'a', 'b', 'c', 0}, + }, + }, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + n := NewNode(tt.name, tt.opts...) + if !reflect.DeepEqual(n, tt.node) { + t.Errorf("NewNode = %v, want %v", n, tt.node) + } + }) + } +} + func TestPruneSubtree(t *testing.T) { subtree1 := &Node{ Name: "child1", @@ -270,6 +322,59 @@ func TestRemoveProperty(t *testing.T) { } } +func TestUpdate(t *testing.T) { + for _, tt := range []struct { + node *Node + prop Property + want *Node + found bool + }{ + { + node: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + {Name: "kaslr-seed", Value: []byte{1, 2, 3}}, + }, + }, + prop: PropertyU64("kaslr-seed", 1), + want: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + {Name: "kaslr-seed", Value: []byte{0, 0, 0, 0, 0, 0, 0, 1}}, + }, + }, + found: true, + }, + { + node: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + }, + }, + prop: PropertyU64("kaslr-seed", 1), + want: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + {Name: "kaslr-seed", Value: []byte{0, 0, 0, 0, 0, 0, 0, 1}}, + }, + }, + found: false, + }, + } { + got := tt.node.Update(tt.prop) + if got != tt.found { + t.Errorf("Node update, want %v, got %v", got, tt.found) + } + if !reflect.DeepEqual(tt.node, tt.want) { + t.Errorf("Node update, want %v, got %v", tt.node, tt.want) + } + } +} + func TestUpdateProperty(t *testing.T) { node := &Node{ Name: "test node", @@ -328,7 +433,7 @@ func TestAsRegion(t *testing.T) { Value: []byte{}, }, want: &Region{}, - wantErr: errPropertyRegionInvalid, + wantErr: ErrPropertyRegionInvalid, }, { name: "read start and size, success", diff --git a/pkg/fb/fb.go b/pkg/fb/fb.go index e57ed68498..f8e9fd6e02 100644 --- a/pkg/fb/fb.go +++ b/pkg/fb/fb.go @@ -62,7 +62,7 @@ func DrawImageAt(img image.Image, posx int, posy int) error { DrawOnBufAt(buf, img, posx, posy, stride, bpp) err = os.WriteFile(fbdev, buf, 0o600) if err != nil { - return fmt.Errorf("error writing to framebuffer: %v", err) + return fmt.Errorf("error writing to framebuffer: %w", err) } return nil } @@ -103,7 +103,7 @@ func DrawScaledImageAt(img image.Image, posx int, posy int, factor int) error { DrawScaledOnBufAt(buf, img, posx, posy, factor, stride, bpp) err = os.WriteFile(fbdev, buf, 0o600) if err != nil { - return fmt.Errorf("error writing to framebuffer: %v", err) + return fmt.Errorf("error writing to framebuffer: %w", err) } return nil } diff --git a/pkg/flash/chips/chips.go b/pkg/flash/chips/chips.go new file mode 100644 index 0000000000..a8402d8f38 --- /dev/null +++ b/pkg/flash/chips/chips.go @@ -0,0 +1,106 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package chips contains chips known to work with the flash tool. +package chips + +import ( + "fmt" + "os" + + "github.com/u-root/u-root/pkg/flash/op" +) + +const ( + k = 1024 + m = 1024 * 1024 +) + +// EraseBlock defines the size and the opcode used for an erase block. +// In Flash Chips, the erase region is aligned to the size. +type EraseBlock struct { + Size int + Op op.OpCode +} + +type ID int + +// Chip defines a chip. +type Chip struct { + Vendor string + Chip string + ID ID + ArraySize int64 + PageSize int64 + SectorSize int64 + BlockSize int64 + Is4BA bool + EraseBlocks []EraseBlock + + WriteEnableInstructionRequired bool + WriteEnableOpcodeSelect op.OpCode + Write op.OpCode + Read op.OpCode +} + +// String implements string for Chip. +func (c *Chip) String() string { + return fmt.Sprintf("Vendor:%s Chip:%s ID:%06x Size:%d 4BA:%v", c.Vendor, c.Chip, c.ID, c.ArraySize, c.Is4BA) +} + +// Lookup finds a Chip by id, returning os.ErrNotExist if it is not found. +func Lookup(id ID) (*Chip, error) { + for _, c := range Chips { + if c.ID == id { + return &c, nil + } + } + return nil, os.ErrNotExist +} + +// Chips are all the chips we know about. +// Note that the test assumes that SST25VF016B +// is first. +var Chips = []Chip{ + { + Vendor: "SST", + Chip: "SST25VF016B", + ID: 0xbf2541, + ArraySize: 2 * m, + // This is the real page size. + // The kernel gets an error on the ioctl. + // PageSize: 256 * 1024, + PageSize: 1, // make it 1 until we get AAI 1024, + SectorSize: 4 * k, + BlockSize: 64 * k, + Is4BA: false, + EraseBlocks: []EraseBlock{ + { + Size: 4 * k, + Op: 0x20, + }, + { + Size: 32 * k, + Op: 0x52, + }, + { + Size: 64 * k, + Op: 0xD8, + }, + { + Size: 2 * m, + Op: 0x60, + }, + { + Size: 2 * m, + Op: 0xc7, + }, + }, + + WriteEnableInstructionRequired: true, + WriteEnableOpcodeSelect: op.WriteEnable, + Write: op.AAI, + Read: op.Read, + }, +} diff --git a/pkg/flash/chips/chips_test.go b/pkg/flash/chips/chips_test.go new file mode 100644 index 0000000000..b5dbc2909d --- /dev/null +++ b/pkg/flash/chips/chips_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package chips_test + +import ( + "errors" + "os" + "reflect" + "testing" + + "github.com/u-root/u-root/pkg/flash/chips" +) + +func TestLookup(t *testing.T) { + tests := []struct { + name string + id chips.ID + want *chips.Chip + err error + }{ + { + name: "Valid ID", + id: 0xbf2541, + want: &chips.Chips[0], // Assume [0] is the SST25VF016B chip + err: nil, + }, + { + name: "Non-existent ID", + id: 0xdeadbeef, + want: nil, + err: os.ErrNotExist, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := chips.Lookup(tt.id) + + if !errors.Is(err, tt.err) { + t.Fatalf("Lookup(%06x) got %v, want %v", tt.id, err, tt.err) + } + + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("Lookup(%d) got %v, want %v", tt.id, got, tt.want) + } + }) + } +} + +func TestString(t *testing.T) { + tests := []struct { + name string + chip chips.Chip + want string + }{ + { + name: "SST25VF016B", + chip: chips.Chips[0], // Assume [0] is the SST25VF016B chip + want: "Vendor:SST Chip:SST25VF016B ID:bf2541 Size:2097152 4BA:false", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.chip.String() + if got != tt.want { + t.Errorf("String() got %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 5a2e68cd73..e0b047fd4a 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -7,10 +7,14 @@ package flash import ( + "bytes" "encoding/binary" "fmt" "io" + "os" + "time" + "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" "github.com/u-root/u-root/pkg/flash/sfdp" "github.com/u-root/u-root/pkg/spidev" @@ -22,6 +26,8 @@ const sfdpMaxAddress = (1 << 24) - 1 // SPI interface for the underlying calls to the SPI driver. type SPI interface { Transfer(transfers []spidev.Transfer) error + ID() (chips.ID, error) + Status() (op.Status, error) } // Flash provides operations for SPI flash chips. @@ -29,21 +35,12 @@ type Flash struct { // spi is the underlying SPI device. spi SPI - // is4ba is true if 4-byte addressing mode is enabled. - is4ba bool - - // size is the size of the flash chip in bytes. - size int64 + // Chip is derived from SFDP or looking up + // the chip via the ID. + chips.Chip // sfdp is cached. sfdp *sfdp.SFDP - - // JEDEC ID is cached. - id uint32 - - pageSize int64 - sectorSize int64 - blockSize int64 } // New creates a new flash device from a SPI interface. @@ -53,36 +50,74 @@ func New(spi SPI) (*Flash, error) { } var err error + var id chips.ID + id, err = f.spi.ID() + if err != nil { + return nil, fmt.Errorf("can not ID chip: %w", err) + } + c, err := chips.Lookup(id) + if err == nil { + f.Chip = *c + // Even when we have a chip, there is still + // benefit to trying to get an SFDP. + // Further, the package as written wants + // some sort of empty sfdp to exist, and this + // is the easiest way to do it. + f.sfdp, _ = sfdp.Read(f.SFDPReader()) + return f, nil + } + f.sfdp, err = sfdp.Read(f.SFDPReader()) if err != nil { - return nil, fmt.Errorf("could not read sfdp: %v", err) + return nil, fmt.Errorf("chip %#x: chip not known, and no SFDP: %w", id, err) + } + if err = f.FillFromSFDP(); err != nil { + return nil, fmt.Errorf("chip %#x: chip not known, and no SFDP: %w", id, err) } + return f, nil +} + +// FillFromSFDP fills the Flash struct with parameters from +// the SFDP. Querying the SFDP is a bit messy, for each type of +// parameter, so this code pulls the SFDP parameters into +// struct members. It also makes the creation of chips a bit easier, +// when we do not have an SFDP. +func (f *Flash) FillFromSFDP() error { density, err := f.SFDP().Param(sfdp.ParamFlashMemoryDensity) if err != nil { - return nil, fmt.Errorf("flash chip SFDP does not have density param") + return fmt.Errorf("flash chip SFDP does not have density param") } if density >= 0x80000000 { - return nil, fmt.Errorf("unsupported flash density: %#x", density) + return fmt.Errorf("unsupported flash density: %#x", density) } - f.size = (density + 1) / 8 + f.ArraySize = (density + 1) / 8 // Assume 4ba if address if size requires 4 bytes. - if f.size >= 0x1000000 { - f.is4ba = true + if f.ArraySize >= 0x1000000 { + f.Is4BA = true + } + + if wer, err := f.SFDP().Param(sfdp.ParamWriteEnableInstructionRequired); err == nil && wer != 0 { + we, err := f.SFDP().Param(sfdp.ParamWriteEnableOpcodeSelect) + if err != nil { + return fmt.Errorf("write enable is required but WriteEnableOpcodeSelect is not in SFDP:%w", err) + } + f.WriteEnableInstructionRequired = true + f.WriteEnableOpcodeSelect = op.OpCode(we) } // TODO - f.pageSize = 256 - f.sectorSize = 4096 - f.blockSize = 65536 + f.PageSize = 256 + f.SectorSize = 4096 + f.BlockSize = 65536 - return f, nil + return nil } // Size returns the size of the flash chip in bytes. func (f *Flash) Size() int64 { - return f.size + return f.ArraySize } const maxTransferSize = 4096 @@ -98,7 +133,7 @@ func min(x, y int64) int64 { func (f *Flash) prepareAddress(addr int64) []byte { data := make([]byte, 4) binary.BigEndian.PutUint32(data, uint32(addr)) - if f.is4ba { + if f.Is4BA { return data } return data[1:] @@ -107,15 +142,15 @@ func (f *Flash) prepareAddress(addr int64) []byte { // ReadAt reads from the flash chip. func (f *Flash) ReadAt(p []byte, off int64) (int, error) { // This is a valid implementation of io.ReaderAt. - if off < 0 || off > f.size { + if off < 0 || off > f.ArraySize { return 0, io.EOF } - p = p[:min(int64(len(p)), f.size-off)] + p = p[:min(int64(len(p)), f.ArraySize-off)] // Split the transfer into maxTransferSize chunks. for i := 0; i < len(p); i += maxTransferSize { if err := f.spi.Transfer([]spidev.Transfer{ - {Tx: append([]byte{op.Read}, f.prepareAddress(off+int64(i))...)}, + {Tx: append(op.Read.Bytes(), f.prepareAddress(off+int64(i))...)}, {Rx: p[i:min(int64(i)+maxTransferSize, int64(len(p)))]}, }); err != nil { return i, err @@ -127,16 +162,52 @@ func (f *Flash) ReadAt(p []byte, off int64) (int, error) { // writeAt performs a write operation without any care for page sizes or // alignment. func (f *Flash) writeAt(p []byte, off int64) (int, error) { - if err := f.spi.Transfer([]spidev.Transfer{ - // Enable writing. - {Tx: []byte{op.WriteEnable}, CSChange: true}, - // Send the address. - {Tx: append([]byte{op.PageProgram}, f.prepareAddress(off)...)}, - // Send the data. - {Tx: p}, - }); err != nil { + t := []spidev.Transfer{ + {Tx: op.PRDRES.Bytes(), CSChange: true}, + } + if f.Chip.WriteEnableInstructionRequired { + t = append(t, spidev.Transfer{Tx: f.Chip.WriteEnableOpcodeSelect.Bytes(), CSChange: true}) + } + // AAAAAAARRRRGHHHHH! + // If this WriteEnable is not here, then the page program request + // NEVER MAKES IT TO THE SPI BUS. + // So, ... put it here, even if not requested, until we figure this out. + // Further, on the macronix part, we can't leave the write disable in, or + // the write enable command ends up being written to the part? + // This is a mess. + t = append(t, spidev.Transfer{Tx: op.WriteEnable.Bytes(), CSChange: true}) + t = append(t, spidev.Transfer{Tx: append(append(op.PageProgram.Bytes(), f.prepareAddress(off)...), p...)}) + if f.Chip.WriteEnableInstructionRequired { + // The meaning of CSChange is ... odd. + // IF CSChange is set true here, then CE# never goes + // high. If CSChange is left unchanged, + // CE# is properly deasserted from the data write above, + // asserted for this command, and deasserted + // at the end. + t = append(t, spidev.Transfer{Tx: op.WriteDisable.Bytes(), DelayUSecs: 10}) + } + if err := f.spi.Transfer(t); err != nil { return 0, err } + // Hang out for a bit, let the part do its thing. + time.Sleep(time.Duration(len(p)) * time.Microsecond) + var i int + for i = 0; i < len(p); i++ { + stat, err := f.spi.Status() + if err != nil { + return len(p), fmt.Errorf("spi status read fails after writing %d bytes:%w", len(p), err) + } + if !stat.Busy() { + break + } + time.Sleep(10 * time.Microsecond) + } + + if i == len(p) { + return len(p), fmt.Errorf("spi busy after writing %d bytes", len(p)) + } + // Between each check for done, sleep about 10 microseconds, to give the part + // a chance to catch its breath. return len(p), nil } @@ -148,38 +219,34 @@ func (f *Flash) writeAt(p []byte, off int64) (int, error) { // what you want instead! func (f *Flash) WriteAt(p []byte, off int64) (int, error) { // This is a valid implementation of io.WriterAt. - if off < 0 || off > f.size { + if off < 0 || off > f.ArraySize { return 0, io.EOF } - p = p[:min(int64(len(p)), f.size-off)] + p = p[:min(int64(len(p)), f.ArraySize-off)] // Special case where no page boundaries are crossed. - if off%f.pageSize+int64(len(p)) <= f.pageSize { + if off%f.PageSize+int64(len(p)) <= f.PageSize { return f.writeAt(p, off) } // Otherwise, there are three regions: // 1. A partial page before the first aligned offset. (optional) - // 2. All the aligned pages in the middle. - // 3. A partial page after the last aligned offset. (optional) - firstAlignedOff := (off + f.pageSize - 1) / f.pageSize * f.pageSize - lastAlignedOff := (off + int64(len(p))) / f.pageSize * f.pageSize + // 2. All the aligned pages + firstAlignedOff := (off + f.PageSize - 1) / f.PageSize * f.PageSize + b := bytes.NewBuffer(p) if off != firstAlignedOff { - if n, err := f.writeAt(p[:firstAlignedOff-off], off); err != nil { + dat := b.Next(int(firstAlignedOff - off)) + if n, err := f.writeAt(dat, off); err != nil { return n, err } } - for i := firstAlignedOff; i < lastAlignedOff; i += f.pageSize { - if _, err := f.writeAt(p[i:i+f.pageSize], off+i); err != nil { + for i := firstAlignedOff; b.Len() > 0; i += f.PageSize { + dat := b.Next(int(f.PageSize)) + if _, err := f.writeAt(dat, i); err != nil { return int(i), err } } - if off+int64(len(p)) != lastAlignedOff { - if _, err := f.writeAt(p[lastAlignedOff-off:], lastAlignedOff); err != nil { - return int(lastAlignedOff - off), err - } - } return len(p), nil } @@ -191,36 +258,53 @@ func (f *Flash) ProgramAt(p []byte, off int64) (int, error) { // EraseAt erases n bytes from offset off. Both parameters must be aligned to // sectorSize. func (f *Flash) EraseAt(n int64, off int64) (int64, error) { - if off < 0 || off > f.size || off+n > f.size { - return 0, io.EOF + if off < 0 || off > f.ArraySize || off+n > f.ArraySize { + return 0, fmt.Errorf("offset (%#x) is < 0, or > %#x, or off+size (%#x) is > f.ArraySize (%#x):%w", off, f.ArraySize, off+n, f.ArraySize, os.ErrInvalid) } - if (off%f.sectorSize != 0) || (n%f.sectorSize != 0) { - return 0, fmt.Errorf("len(p) and off must be multiple of the sector size") + if (off%f.SectorSize != 0) || (n%f.SectorSize != 0) { + return 0, fmt.Errorf("offset (%#x) and size (%#x) must be multiple of the sector size(%#x):%w", off, n, f.SectorSize, os.ErrInvalid) } for i := int64(0); i < n; { opcode := op.SectorErase - eraseSize := f.sectorSize + eraseSize := f.SectorSize // Optimization to erase faster. - if i%f.blockSize == 0 && n-i > f.blockSize { + if i%f.BlockSize == 0 && n-i > f.BlockSize { opcode = op.BlockErase - eraseSize = f.blockSize + eraseSize = f.BlockSize } if err := f.spi.Transfer([]spidev.Transfer{ // Enable writing. { - Tx: []byte{op.WriteEnable}, + Tx: op.WriteEnable.Bytes(), CSChange: true, }, // Send the address. - {Tx: append([]byte{opcode}, f.prepareAddress(off+i)...)}, + {Tx: append(opcode.Bytes(), f.prepareAddress(off+i)...)}, }); err != nil { return i, err } + var spin int + // Give it 100 tries, which is 1000 ms + for spin = 0; spin <= 100; spin++ { + time.Sleep(10 * time.Millisecond) + stat, err := f.spi.Status() + if err != nil { + return n, fmt.Errorf("spi status read fails after erasing %#x:%w", off+i, err) + } + if !stat.Busy() { + break + } + } + + if spin > 100 { + return i, fmt.Errorf("spi busy after erasing %d bytes", i) + } + i += eraseSize } return n, nil @@ -228,18 +312,8 @@ func (f *Flash) EraseAt(n int64, off int64) (int64, error) { // ReadJEDECID reads the flash chip's JEDEC ID. func (f *Flash) ReadJEDECID() (uint32, error) { - tx := []byte{op.ReadJEDECID} - rx := make([]byte, 3) - - if err := f.spi.Transfer([]spidev.Transfer{ - {Tx: tx}, - {Rx: rx}, - }); err != nil { - return 0, err - } - - // Little-endian - return (uint32(rx[0]) << 16) | (uint32(rx[1]) << 8) | uint32(rx[2]), nil + id, err := f.spi.ID() + return uint32(id), err } // SFDPReader is used to read from the SFDP address space. @@ -263,7 +337,7 @@ func (f *SFDPReader) ReadAt(p []byte, off int64) (int, error) { } p = p[:min(int64(len(p)), sfdpMaxAddress-off)] tx := []byte{ - op.ReadSFDP, + byte(op.ReadSFDP), // offset, 3-bytes, big-endian byte((off >> 16) & 0xff), byte((off >> 8) & 0xff), byte(off & 0xff), // dummy 0xff diff --git a/pkg/flash/flash_linux_test.go b/pkg/flash/flash_linux_test.go index e40824777f..5aa6169628 100644 --- a/pkg/flash/flash_linux_test.go +++ b/pkg/flash/flash_linux_test.go @@ -15,6 +15,7 @@ import ( // TestSFDPReader tests reading arbitrary offsets from the SFDP. func TestSFDPReader(t *testing.T) { + fakeErr := errors.New("fake transfer error") for _, tt := range []struct { name string readOffset int64 @@ -40,16 +41,16 @@ func TestSFDPReader(t *testing.T) { name: "transfer error", readOffset: 0x10, readSize: 4, - forceTransferErr: errors.New("fake transfer error"), - wantNewErr: errors.New("could not read sfdp: fake transfer error"), + forceTransferErr: fakeErr, + wantNewErr: fakeErr, }, } { t.Run(tt.name, func(t *testing.T) { s := spimock.New() s.ForceTransferErr = tt.forceTransferErr f, err := New(s) - if gotErrString, wantErrString := fmt.Sprint(err), fmt.Sprint(tt.wantNewErr); gotErrString != wantErrString { - t.Errorf("flash.New() err = %q; want %q", gotErrString, wantErrString) + if !errors.Is(err, tt.wantNewErr) { + t.Errorf("flash.New() err = %v; want %v", err, tt.forceTransferErr) } if err != nil { return @@ -80,6 +81,10 @@ func TestSFDPReadDWORD(t *testing.T) { } sfdp := f.SFDP() + if sfdp == nil { + t.Fatalf("f.SFDP: got nil, want value") + } + dword, err := sfdp.Dword(0, 0) if err != nil { t.Error(err) diff --git a/pkg/flash/op/op.go b/pkg/flash/op/op.go index 9acc3cce80..5f14ff601f 100644 --- a/pkg/flash/op/op.go +++ b/pkg/flash/op/op.go @@ -6,29 +6,115 @@ // the beginning of a SPI transaction. package op +import ( + "fmt" + "strings" +) + +type OpCode byte + const ( // PageProgram programs a page on the flash chip. - PageProgram byte = 0x02 + PageProgram OpCode = 0x02 // Read reads from the flash chip. - Read byte = 0x03 + Read OpCode = 0x03 // WriteDisable disables writing. - WriteDisable byte = 0x04 + WriteDisable OpCode = 0x04 // ReadStatus reads the status register. - ReadStatus byte = 0x05 + ReadStatus OpCode = 0x05 // WriteEnable enables writing. - WriteEnable byte = 0x06 + WriteEnable OpCode = 0x06 // SectorErase erases a sector to the value 0xff. - SectorErase byte = 0x20 + SectorErase OpCode = 0x20 // ReadSFDP reads from the SFDP. - ReadSFDP byte = 0x5a + ReadSFDP OpCode = 0x5a // ReadID reads the JEDEC ID. - ReadJEDECID byte = 0x9f + ReadJEDECID OpCode = 0x9f // PRD/RES - PRDRES = 0xab - // Enter4BA enters 4-byte addressing mode. - Enter4BA byte = 0xb7 + PRDRES OpCode = 0xab + // AAI is auto address increment + AAI OpCode = 0xad + // Enter4BA enters 4-OpCode addressing mode. + Enter4BA OpCode = 0xb7 // BlockErase erases a block to the value 0xff. - BlockErase byte = 0xd8 - // Exit4BA exits 4-byte addressing mode. - Exit4BA byte = 0xe9 + BlockErase OpCode = 0xd8 + // Exit4BA exits 4-OpCode addressing mode. + Exit4BA OpCode = 0xe9 +) + +func (o OpCode) String() string { + switch o { + case PageProgram: + return "PageProgram" + case Read: + return "Read" + case WriteDisable: + return "WriteDisable" + case ReadStatus: + return "ReadStatus" + case WriteEnable: + return "WriteEnable" + case SectorErase: + return "SectorErase" + case ReadSFDP: + return "ReadSFDP" + case ReadJEDECID: + return "ReadJEDECID" + case PRDRES: + return "PRDRES" + case AAI: + return "AAI" + case Enter4BA: + return "Enter4BA" + case BlockErase: + return "BlockErase" + case Exit4BA: + return "Exit4BA" + default: + return fmt.Sprintf("Unknown(%02x)", byte(o)) + } +} + +func (o OpCode) Bytes() []byte { + return []byte{byte(o)} +} + +type Status byte + +// Status is not universally defined, but a few bits are common. +const ( + WriteBusy Status = 1 << iota + WriteEnabled + ByteProtect0 + ByteProtect1 + ByteProtect2 + ByteProtectP3 + AutoAddressIncrement + ByteProtectLocked ) + +var names = []string{ + "WriteBusy", + "WriteEnabled", + "ByteProtect0", + "ByteProtect1", + "ByteProtect2", + "ByteProtectP3", + "AutoAddressIncrement", + "ByteProtectLocked", +} + +func (status Status) String() string { + var s string + for i := 0; i < 8; i++ { + if byte(status)&(1<= ngpio { return 0, fmt.Errorf("requested pin %d of controller %s, but controller only has %d pins", pin, controller, ngpio) @@ -104,11 +104,11 @@ func SetOutputValue(pin int, val Value) error { path := filepath.Join(gpioPath, fmt.Sprintf("gpio%d", pin), "direction") outFile, err := os.OpenFile(path, os.O_WRONLY, 0) if err != nil { - return fmt.Errorf("failed to open %s: %v", path, err) + return fmt.Errorf("failed to open %s: %w", path, err) } defer outFile.Close() if _, err := outFile.WriteString(dir); err != nil { - return fmt.Errorf("failed to set gpio %d to %s: %v", pin, dir, err) + return fmt.Errorf("failed to set gpio %d to %s: %w", pin, dir, err) } return nil } @@ -119,7 +119,7 @@ func ReadValue(pin int) (Value, error) { path := filepath.Join(gpioPath, fmt.Sprintf("gpio%d", pin), "value") buf, err := os.ReadFile(path) if err != nil { - return Low, fmt.Errorf("failed to read value of gpio %d: %v", pin, err) + return Low, fmt.Errorf("failed to read value of gpio %d: %w", pin, err) } switch string(buf) { case "0\n": @@ -135,11 +135,11 @@ func Export(pin int) error { path := filepath.Join(gpioPath, "export") outFile, err := os.OpenFile(path, os.O_WRONLY, 0) if err != nil { - return fmt.Errorf("failed to open %s: %v", path, err) + return fmt.Errorf("failed to open %s: %w", path, err) } defer outFile.Close() if _, err := outFile.WriteString(strconv.Itoa(pin)); err != nil { - return fmt.Errorf("failed to export gpio %d: %v", pin, err) + return fmt.Errorf("failed to export gpio %d: %w", pin, err) } return nil } diff --git a/pkg/gzip/file.go b/pkg/gzip/file.go index 28abc0032a..74a6061b68 100644 --- a/pkg/gzip/file.go +++ b/pkg/gzip/file.go @@ -9,7 +9,7 @@ import ( "os" "strings" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // File is a file path to be compressed or decompressed. diff --git a/pkg/ipmi/ocp/ocp_linux.go b/pkg/ipmi/ocp/ocp_linux.go index c2545860ed..33a53e665f 100644 --- a/pkg/ipmi/ocp/ocp_linux.go +++ b/pkg/ipmi/ocp/ocp_linux.go @@ -341,12 +341,12 @@ func GetOemIpmiBootDriveInfo(si *smbios.Info) (*BootDriveInfo, error) { const bootDriveName = "Boot_Drive" var info BootDriveInfo - if boardManufacturerID, ok := OENMap[t1.Manufacturer]; ok == true { + if boardManufacturerID, ok := OENMap[t1.Manufacturer]; ok { info.ManufacturerID = boardManufacturerID } for index := 0; index < len(t9); index++ { - if strings.Contains(t9[index].SlotDesignation, bootDriveName) == false { + if !strings.Contains(t9[index].SlotDesignation, bootDriveName) { continue } diff --git a/pkg/mount/mtd/linux_test.go b/pkg/mount/mtd/linux_test.go index 4cbf0facf4..6644d1f3cf 100644 --- a/pkg/mount/mtd/linux_test.go +++ b/pkg/mount/mtd/linux_test.go @@ -15,19 +15,20 @@ var ( ) func TestOpen(t *testing.T) { - if _, err := os.Stat(DevName); err != nil { + m, err := NewDev(DevName) + if err != nil { tmpDir := t.TempDir() testmtd, err := os.CreateTemp(tmpDir, "testmtd") if err != nil { t.Errorf(`os.Create(tmpDir, "testmtd")=file, %q, want file, nil`, err) } DevName = testmtd.Name() + m, err = NewDev(DevName) + if err != nil { + t.Fatal(err) + } } - m, err := NewDev(DevName) - if err != nil { - t.Fatal(err) - } defer m.Close() if _, err := m.QueueWriteAt([]byte(testString), 0); err != nil { diff --git a/pkg/securelaunch/launcher/launcher.go b/pkg/securelaunch/launcher/launcher.go index f3ceb890d3..ad5ce8a448 100644 --- a/pkg/securelaunch/launcher/launcher.go +++ b/pkg/securelaunch/launcher/launcher.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/mount" slaunch "github.com/u-root/u-root/pkg/securelaunch" "github.com/u-root/u-root/pkg/securelaunch/measurement" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // Launcher describes the "launcher" section of policy file. diff --git a/pkg/sh/run.go b/pkg/sh/run.go index 31cff75694..760acf82c3 100644 --- a/pkg/sh/run.go +++ b/pkg/sh/run.go @@ -19,10 +19,10 @@ func Run(arg0 string, args ...string) error { // RunWithLogs runs a command with stdin, stdout and stderr. This function is // more verbose than log.Run. func RunWithLogs(arg0 string, args ...string) error { - log.Printf("executing command %q with args %q", arg0, args) + log.Printf("Executing command %q with args %q...", arg0, args) err := RunWithIO(os.Stdin, os.Stdout, os.Stderr, arg0, args...) if err != nil { - log.Printf("command %q with args %q failed: %v", arg0, args, err) + log.Printf("Command %q with args %q failed: %v", arg0, args, err) } return err } diff --git a/pkg/spidev/spidev_linux.go b/pkg/spidev/spidev_linux.go index 72e63efd0f..d27ccbae92 100644 --- a/pkg/spidev/spidev_linux.go +++ b/pkg/spidev/spidev_linux.go @@ -7,13 +7,17 @@ package spidev import ( + "bytes" "encoding/binary" "fmt" + "io" "math" "os" "runtime" "unsafe" + "github.com/u-root/u-root/pkg/flash/chips" + "github.com/u-root/u-root/pkg/flash/op" "golang.org/x/sys/unix" ) @@ -141,6 +145,12 @@ type Transfer struct { WordDelayUSecs uint8 } +func (t *Transfer) String() string { + var x [8]byte + n, _ := io.ReadAtLeast(bytes.NewBuffer(t.Tx), x[:], len(x)) + return fmt.Sprintf("%#02x...[:%d](%s)", x[:n], len(t.Tx), op.OpCode(x[0]).String()) +} + // ErrTxOverflow is returned if the Transfer buffer is too large. type ErrTxOverflow struct { TxLen, TxMax int @@ -177,24 +187,60 @@ type SPI struct { f *os.File // Used for mocking. syscall func(trap, a1, a2 uintptr, a3 unsafe.Pointer) (r1, r2 uintptr, err unix.Errno) + // logger allows logging + logger func(string, ...any) +} + +type opt func(s *SPI) error + +// WithLogger returns an opt which can be used in Open to add +// a logger. A common usage would be: +// spidev.Open("/dev/spidev0.0", WithLogger(log.Printf)) +func WithLogger(l func(string, ...any)) opt { + return func(s *SPI) error { + s.logger = l + return nil + } +} + +// safe tries to set "safe" settings for initial SPI operation. +// However, settings may not succeed, for $REASONS$. +// Hardware is highly variable. +// If there is an error, log it, and continue. +func (s *SPI) safe() { + if err := s.SetSpeedHz(500000); err != nil { + s.logger("warning only: set speed to %d HZ err %v", 500000, err) + } } // Open opens a new SPI device. dev is a filename such as "/dev/spidev0.0". // Remember to call Close() once done. -func Open(dev string) (*SPI, error) { +func Open(dev string, opts ...opt) (*SPI, error) { f, err := os.OpenFile(dev, os.O_RDWR, 0) if err != nil { return nil, err } - return &SPI{ - f: f, + s := &SPI{ + f: f, + logger: func(string, ...any) {}, // log.Printf, + //logger: log.Printf, // a3 must be an unsafe.Pointer instead of a uintptr, otherwise // we cannot mock out in the test without creating a race // condition. See `go doc unsafe.Pointer`. syscall: func(trap, a1, a2 uintptr, a3 unsafe.Pointer) (r1, r2 uintptr, err unix.Errno) { return unix.Syscall(trap, a1, a2, uintptr(a3)) }, - }, err + } + + for _, o := range opts { + if err := o(s); err != nil { + return nil, err + } + } + + s.safe() + + return s, nil } // Close closes the SPI device. @@ -208,6 +254,7 @@ func (s *SPI) Transfer(transfers []Transfer) error { // Convert []Transfer to []iocTransfer. it := make([]iocTransfer, len(transfers)) for i, t := range transfers { + s.logger("%d:%s", i, t.String()) it[i] = iocTransfer{ speedHz: t.SpeedHz, delayUSecs: t.DelayUSecs, @@ -301,3 +348,58 @@ func (s *SPI) SetSpeedHz(hz uint32) error { } return nil } + +// ID gets ID. +func (s *SPI) ID() (chips.ID, error) { + // Wake it up, then get the id. + // PRDRES is not universally handled on all devices, but that's ok. + // but CE MUST drop, so we structure this as two separate + // transfers to ensure that happens. + var id [4]byte + transfers := []Transfer{ + { + Tx: []byte{byte(op.PRDRES)}, + Rx: make([]byte, 1), + CSChange: true, + }, + { + Tx: []byte{byte(op.ReadJEDECID), 0, 0, 0}, + Rx: id[:], + }, + } + + if err := s.Transfer(transfers); err != nil { + return -1, err + } + + id[0] = 0 + return chips.ID(binary.BigEndian.Uint32(id[:])), nil +} + +// Status gets the 8 bit status register. +// While this is similar to ID, there is a good chance +// there will be special cases for each opcode type, +// so they should probably remain separate. +// SPI is always full of surprises. +func (s *SPI) Status() (op.Status, error) { + var status [2]byte + transfers := []Transfer{ + { + Tx: []byte{byte(op.PRDRES)}, + Rx: make([]byte, 1), + CSChange: true, + }, + { + Tx: []byte{byte(op.ReadStatus), 0}, + Rx: status[:], + }, + } + + // in the event of an error, return all 1s, + // making the chip look busy. + if err := s.Transfer(transfers); err != nil { + return op.Status(0xff), err + } + + return op.Status(status[1]), nil +} diff --git a/pkg/spidev/spidev_linux_test.go b/pkg/spidev/spidev_linux_test.go index 7ab7552a9d..0cedd3f9cc 100644 --- a/pkg/spidev/spidev_linux_test.go +++ b/pkg/spidev/spidev_linux_test.go @@ -13,6 +13,8 @@ import ( "testing" "unsafe" + "github.com/u-root/u-root/pkg/flash/chips" + "github.com/u-root/u-root/pkg/flash/op" "golang.org/x/sys/unix" ) @@ -130,6 +132,20 @@ func TestGetters(t *testing.T) { } { m.forceErrno = tt.forceErrno + t.Run("ID"+tt.name, func(t *testing.T) { + m, err := s.ID() + if !errors.Is(err, tt.wantErr) { + t.Errorf("Mode() got error %q; want error %q", err, tt.wantErr) + } + if err != nil { + return + } + want := chips.ID(0) + if m != want { + t.Errorf("ID() = %#v; want %#v", m, want) + } + }) + t.Run("Mode"+tt.name, func(t *testing.T) { m, err := s.Mode() if !errors.Is(err, tt.wantErr) { @@ -243,6 +259,32 @@ func TestSetters(t *testing.T) { } } +func TestTransferString(t *testing.T) { + for _, tt := range []struct { + n string + t Transfer + s string + }{ + { + n: "empty", + t: Transfer{}, + s: "00...[:0](Unknown(00))", + }, + { + n: "Read with no data", + t: Transfer{Tx: op.Read.Bytes()}, + s: "0x03...[:1](Read)", + }, + } { + t.Run(tt.n, func(t *testing.T) { + s := tt.t.String() + if s != tt.s { + t.Fatalf("got %q, want %q", s, tt.s) + } + }) + } +} + // TestTransfer tests multiple scenarios involving the Transfer method. func TestTransfer(t *testing.T) { // To avoid OOMing the CI, we set the maxTransferSize to a smaller diff --git a/pkg/strace/syscalls_test.go b/pkg/strace/syscalls_test.go index b9381fe9c3..95cbbfae52 100644 --- a/pkg/strace/syscalls_test.go +++ b/pkg/strace/syscalls_test.go @@ -20,8 +20,8 @@ func TestByName(t *testing.T) { val uintptr ret error }{ - {name: "open", val: unix.SYS_OPEN, ret: nil}, - {name: "Xopen", val: unix.SYS_OPEN, ret: fmt.Errorf("Xopen:not found")}, + {name: "read", val: unix.SYS_READ, ret: nil}, + {name: "Xread", val: unix.SYS_READ, ret: fmt.Errorf("Xread:not found")}, } { n, err := ByName(tt.name) if err != nil && tt.ret == nil { @@ -42,8 +42,8 @@ func TestByNum(t *testing.T) { val uintptr ret error }{ - {name: "open", val: unix.SYS_OPEN, ret: nil}, - {name: "bogus", val: 10000000, ret: fmt.Errorf("Xopen:not found")}, + {name: "read", val: unix.SYS_READ, ret: nil}, + {name: "bogus", val: 10000000, ret: fmt.Errorf("Xread:not found")}, } { n, err := ByNumber(tt.val) if err != nil && tt.ret == nil { diff --git a/pkg/strace/tracer_test.go b/pkg/strace/tracer_test.go index cdd2002676..44965f7e80 100644 --- a/pkg/strace/tracer_test.go +++ b/pkg/strace/tracer_test.go @@ -16,7 +16,7 @@ import ( "time" "github.com/u-root/u-root/pkg/testutil" - "github.com/u-root/u-root/pkg/uio/uiotest" + "github.com/u-root/uio/uio/uiotest" ) func prepareTestCmd(t *testing.T, cmd string) { diff --git a/pkg/syscallfilter/syscallfilter_linux.go b/pkg/syscallfilter/syscallfilter_linux.go index 445769f4cf..0096d0cb35 100644 --- a/pkg/syscallfilter/syscallfilter_linux.go +++ b/pkg/syscallfilter/syscallfilter_linux.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && amd64 -// +build linux,amd64 +//go:build (linux && arm64) || (linux && amd64) || (linux && riscv64) +// +build linux,arm64 linux,amd64 linux,riscv64 package syscallfilter @@ -100,15 +100,16 @@ func eventName(r *strace.TraceRecord) string { case strace.SyscallExit: return "X" + sysname case strace.SignalExit: - return fmt.Sprintf("SignalExit") + return "SignalExit" case strace.Exit: - return fmt.Sprintf("Exit") + return "Exit" case strace.SignalStop: - return fmt.Sprintf("SignalStop") + return "SignalStop" case strace.NewChild: - return fmt.Sprintf("NewChild") + return "NewChild" + default: + log.Panicf("Unknown event %#x from record %v", r.Event, r) } - log.Panicf("Unknown event %#x from record %v", r.Event, r) return "" } @@ -145,19 +146,13 @@ func (c *Cmd) handleEvent(t strace.Task, r *strace.TraceRecord, e []*event) erro // as created by AddActions. The slice can be empty, in which case the command // runs as normal. func (c *Cmd) Run() error { - defer func() { - // This wait may or may not be needed, since the process - // can end normally or be stopped by a filter. Hence, - // we will not check for an error. - c.Wait() - }() - if err := strace.Trace(c.Cmd, func(t strace.Task, r *strace.TraceRecord) error { - ret := c.handleEvent(t, r, c.events) - return ret - }); err != nil { - return fmt.Errorf("%v", err) - } - return nil + // This wait may or may not be needed, since the process + // can end normally or be stopped by a filter. Hence, + // we will not check for an error. + defer c.Wait() + return strace.Trace(c.Cmd, func(t strace.Task, r *strace.TraceRecord) error { + return c.handleEvent(t, r, c.events) + }) } // AddActions creates an []event as defined by a possibly empty set of actions, and diff --git a/pkg/syscallfilter/syscallfilter_linux_test.go b/pkg/syscallfilter/syscallfilter_linux_test.go index 8e33a2ec36..4b213dcc43 100644 --- a/pkg/syscallfilter/syscallfilter_linux_test.go +++ b/pkg/syscallfilter/syscallfilter_linux_test.go @@ -9,6 +9,7 @@ import ( "os" "strconv" "strings" + "syscall" "testing" "github.com/u-root/u-root/pkg/strace" @@ -224,8 +225,8 @@ func TestEventName(t *testing.T) { r *strace.TraceRecord n string }{ - {r: &strace.TraceRecord{Event: strace.SyscallEnter, Syscall: &strace.SyscallEvent{Sysno: 0}}, n: "Eread"}, - {r: &strace.TraceRecord{Event: strace.SyscallExit, Syscall: &strace.SyscallEvent{Sysno: 0}}, n: "Xread"}, + {r: &strace.TraceRecord{Event: strace.SyscallEnter, Syscall: &strace.SyscallEvent{Sysno: syscall.SYS_READ}}, n: "Eread"}, + {r: &strace.TraceRecord{Event: strace.SyscallExit, Syscall: &strace.SyscallEvent{Sysno: syscall.SYS_READ}}, n: "Xread"}, {r: &strace.TraceRecord{Event: strace.SyscallEnter, Syscall: &strace.SyscallEvent{Sysno: 0xabcd}}, n: "Eabcd"}, {r: &strace.TraceRecord{Event: strace.SyscallExit, Syscall: &strace.SyscallEvent{Sysno: 0xbcde}}, n: "Xbcde"}, {r: &strace.TraceRecord{Event: strace.SignalExit}, n: "SignalExit"}, diff --git a/pkg/tarutil/tar.go b/pkg/tarutil/tar.go index 0f19abb14a..76587c1deb 100644 --- a/pkg/tarutil/tar.go +++ b/pkg/tarutil/tar.go @@ -83,10 +83,10 @@ func ExtractDir(tarFile io.Reader, dir string, opts *Opts) error { fi, err := os.Stat(dir) if os.IsNotExist(err) { if err := os.Mkdir(dir, os.ModePerm); err != nil { - return fmt.Errorf("could not create directory %s: %v", dir, err) + return fmt.Errorf("could not create directory %s: %w", dir, err) } } else if err != nil || !fi.IsDir() { - return fmt.Errorf("could not stat directory %s: %v", dir, err) + return fmt.Errorf("could not stat directory %s: %w", dir, err) } return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error { @@ -196,10 +196,7 @@ func CreateTar(tarFile io.Writer, files []string, opts *Opts) error { return err } } - if err := tw.Close(); err != nil { - return err - } - return nil + return tw.Close() } func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error { @@ -248,7 +245,7 @@ func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error { } if err := os.Chmod(path, fi.Mode()&os.ModePerm); err != nil { - return fmt.Errorf("error setting mode %#o on %q: %v", + return fmt.Errorf("error setting mode %#o on %q: %w", fi.Mode()&os.ModePerm, path, err) } // TODO: also set ownership, etc... @@ -260,11 +257,6 @@ func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error { // the file is omitted. type Filter func(hdr *tar.Header) bool -// NoFilter does not filter or modify any files. -func NoFilter(hdr *tar.Header) bool { - return true -} - // VerboseFilter prints the name of every file. func VerboseFilter(hdr *tar.Header) bool { fmt.Println(hdr.Name) diff --git a/pkg/tarutil/tar_test.go b/pkg/tarutil/tar_test.go index 0b1901734e..a042a5df36 100644 --- a/pkg/tarutil/tar_test.go +++ b/pkg/tarutil/tar_test.go @@ -45,7 +45,7 @@ func TestExtractDir(t *testing.T) { {"a.txt", "hello\n"}, {"dir/b.txt", "world\n"}, } - extractAndCompare(t, "test.tar", files) + extractAndCompare(t, "testdata/test.tar", files) } func TestCreateTarSingleFile(t *testing.T) { @@ -57,7 +57,10 @@ func TestCreateTarSingleFile(t *testing.T) { if err != nil { t.Fatal(err) } - if err := CreateTar(f, []string{"test0"}, nil); err != nil { + if err := CreateTar(f, []string{"testdata/test0"}, &Opts{ + NoRecursion: true, + Filters: []Filter{VerboseFilter, VerboseLogFilter}, + }); err != nil { f.Close() t.Fatal(err) } @@ -69,11 +72,7 @@ func TestCreateTarSingleFile(t *testing.T) { if err != nil { t.Fatalf("system tar could not parse the file: %v", err) } - expected := `test0 -test0/a.txt -test0/dir -test0/dir/b.txt -` + expected := "testdata/test0\n" if string(out) != expected { t.Fatalf("got %q, want %q", string(out), expected) } @@ -88,7 +87,7 @@ func TestCreateTarMultFiles(t *testing.T) { if err != nil { t.Fatal(err) } - files := []string{"test0", "test1", "test2.txt"} + files := []string{"testdata/test0", "testdata/test1", "testdata/test2.txt"} if err := CreateTar(f, files, nil); err != nil { f.Close() t.Fatal(err) @@ -101,13 +100,13 @@ func TestCreateTarMultFiles(t *testing.T) { if err != nil { t.Fatalf("system tar could not parse the file: %v", err) } - expected := `test0 -test0/a.txt -test0/dir -test0/dir/b.txt -test1 -test1/a1.txt -test2.txt + expected := `testdata/test0 +testdata/test0/a.txt +testdata/test0/dir +testdata/test0/dir/b.txt +testdata/test1 +testdata/test1/a1.txt +testdata/test2.txt ` if string(out) != expected { t.Fatalf("got %q, want %q", string(out), expected) @@ -168,7 +167,7 @@ func TestCreateTarProcfsFile(t *testing.T) { } func TestListArchive(t *testing.T) { - f, err := os.Open("test.tar") + f, err := os.Open("testdata/test.tar") if err != nil { t.Fatal(err) } diff --git a/pkg/tarutil/test.tar b/pkg/tarutil/testdata/test.tar similarity index 100% rename from pkg/tarutil/test.tar rename to pkg/tarutil/testdata/test.tar diff --git a/pkg/tarutil/test0/a.txt b/pkg/tarutil/testdata/test0/a.txt similarity index 100% rename from pkg/tarutil/test0/a.txt rename to pkg/tarutil/testdata/test0/a.txt diff --git a/pkg/tarutil/test0/dir/b.txt b/pkg/tarutil/testdata/test0/dir/b.txt similarity index 100% rename from pkg/tarutil/test0/dir/b.txt rename to pkg/tarutil/testdata/test0/dir/b.txt diff --git a/pkg/tarutil/test1/a1.txt b/pkg/tarutil/testdata/test1/a1.txt similarity index 100% rename from pkg/tarutil/test1/a1.txt rename to pkg/tarutil/testdata/test1/a1.txt diff --git a/pkg/tarutil/test2.txt b/pkg/tarutil/testdata/test2.txt similarity index 100% rename from pkg/tarutil/test2.txt rename to pkg/tarutil/testdata/test2.txt diff --git a/pkg/termios/sgtty_netbsd.go b/pkg/termios/sgtty_netbsd.go new file mode 100644 index 0000000000..784a3d4ce9 --- /dev/null +++ b/pkg/termios/sgtty_netbsd.go @@ -0,0 +1,18 @@ +// Copyright 2021 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package termios + +import "golang.org/x/sys/unix" + +const ( + gets = unix.TIOCGETA + sets = unix.TIOCSETA + getWinSize = unix.TIOCGWINSZ + setWinSize = unix.TIOCSWINSZ +) + +func speed(speed int) int32 { + return int32(speed) +} diff --git a/pkg/termios/sgtty_unix.go b/pkg/termios/sgtty_unix.go index 4d306e3945..f561d16437 100644 --- a/pkg/termios/sgtty_unix.go +++ b/pkg/termios/sgtty_unix.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !plan9 && !windows && !darwin && !freebsd && !openbsd -// +build !plan9,!windows,!darwin,!freebsd,!openbsd +//go:build !plan9 && !windows && !darwin && !freebsd && !openbsd && !netbsd +// +build !plan9,!windows,!darwin,!freebsd,!openbsd,!netbsd package termios diff --git a/pkg/termios/termios_bsd.go b/pkg/termios/termios_bsd.go index 2ff943cc8e..30fb0ec751 100644 --- a/pkg/termios/termios_bsd.go +++ b/pkg/termios/termios_bsd.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || freebsd || openbsd -// +build darwin freebsd openbsd +//go:build darwin || freebsd || openbsd || netbsd +// +build darwin freebsd openbsd netbsd package termios diff --git a/pkg/termios/var_netbsd.go b/pkg/termios/var_netbsd.go new file mode 100644 index 0000000000..b29418b30a --- /dev/null +++ b/pkg/termios/var_netbsd.go @@ -0,0 +1,33 @@ +// Copyright 2021 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package termios + +import ( + "golang.org/x/sys/unix" +) + +// baud2unixB convert a baudrate to the corresponding unix const. +var baud2unixB = map[int]int32{ + 50: unix.B50, + 75: unix.B75, + 110: unix.B110, + 134: unix.B134, + 150: unix.B150, + 200: unix.B200, + 300: unix.B300, + 600: unix.B600, + 1200: unix.B1200, + 1800: unix.B1800, + 2400: unix.B2400, + 4800: unix.B4800, + 9600: unix.B9600, + 19200: unix.B19200, + 38400: unix.B38400, + 57600: unix.B57600, + 115200: unix.B115200, + 230400: unix.B230400, +} + +func toTermiosCflag(r int32) uint32 { return uint32(r) } diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 5f68623d7b..96b4f87a33 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -15,7 +15,6 @@ import ( "time" "github.com/u-root/gobusybox/src/pkg/golang" - "github.com/u-root/u-root/pkg/cmdline" ) // CheckError is a helper function for tests @@ -154,11 +153,8 @@ func Run(m *testing.M, mainFn func()) { } // SkipIfInVMTest skips a test if it's being executed in a u-root test VM. -// -// See pkg/vmtest/integration.go which starts the VM with the uroot.vmtest in -// the kernel cmdline. func SkipIfInVMTest(t *testing.T) { - if cmdline.ContainsFlag("uroot.vmtest") { + if os.Getenv("VMTEST_IN_GUEST") == "1" { t.Skipf("Skipping test since we are in a u-root test VM") } } diff --git a/pkg/uio/alignreader.go b/pkg/uio/alignreader.go deleted file mode 100644 index 6800644148..0000000000 --- a/pkg/uio/alignreader.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "io" -) - -// AlignReader keeps track of how many bytes were read so the reader can be -// aligned at a future time. -type AlignReader struct { - R io.Reader - N int -} - -// Read reads from the underlying io.Reader. -func (r *AlignReader) Read(b []byte) (int, error) { - n, err := r.R.Read(b) - r.N += n - return n, err -} - -// ReadByte reads one byte from the underlying io.Reader. -func (r *AlignReader) ReadByte() (byte, error) { - b := make([]byte, 1) - _, err := io.ReadFull(r, b) - return b[0], err -} - -// Align aligns the reader to the given number of bytes and returns the -// bytes read to pad it. -func (r *AlignReader) Align(n int) ([]byte, error) { - if r.N%n == 0 { - return []byte{}, nil - } - pad := make([]byte, n-r.N%n) - m, err := io.ReadFull(r, pad) - return pad[:m], err -} diff --git a/pkg/uio/alignwriter.go b/pkg/uio/alignwriter.go deleted file mode 100644 index 95a52f61fa..0000000000 --- a/pkg/uio/alignwriter.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" -) - -// AlignWriter keeps track of how many bytes were written so the writer can be -// aligned at a future time. -type AlignWriter struct { - W io.Writer - N int -} - -// Write writes to the underlying io.Writew. -func (w *AlignWriter) Write(b []byte) (int, error) { - n, err := w.W.Write(b) - w.N += n - return n, err -} - -// Align aligns the writer to the given number of bytes using the given pad -// value. -func (w *AlignWriter) Align(n int, pad byte) error { - if w.N%n == 0 { - return nil - } - _, err := w.Write(bytes.Repeat([]byte{pad}, n-w.N%n)) - return err -} diff --git a/pkg/uio/archivereader.go b/pkg/uio/archivereader.go deleted file mode 100644 index 4a3a9fc068..0000000000 --- a/pkg/uio/archivereader.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "errors" - "io" - - "github.com/pierrec/lz4/v4" -) - -const ( - // preReadSizeBytes is the num of bytes pre-read from a io.Reader that will - // be used to match against archive header. - defaultArchivePreReadSizeBytes = 1024 -) - -var ErrPreReadError = errors.New("pre-read nothing") - -// ArchiveReader reads from a io.Reader, decompresses source bytes -// when applicable. -// -// It allows probing for multiple archive format, while still able -// to read from beginning, by pre-reading a small number of bytes. -// -// Always use newArchiveReader to initialize. -type ArchiveReader struct { - // src is where we read source bytes. - src io.Reader - // buf stores pre-read bytes from original io.Reader. Archive format - // detection will be done against it. - buf []byte - - // preReadSizeBytes is how many bytes we pre-read for magic number - // matching for each archive type. This should be greater than or - // equal to the largest header frame size of each supported archive - // format. - preReadSizeBytes int -} - -func NewArchiveReader(r io.Reader) (ArchiveReader, error) { - ar := ArchiveReader{ - src: r, - // Randomly chosen, should be enough for most types: - // - // e.g. gzip with 10 byte header, lz4 with a header size - // between 7 and 19 bytes. - preReadSizeBytes: defaultArchivePreReadSizeBytes, - } - pbuf := make([]byte, ar.preReadSizeBytes) - - nr, err := io.ReadFull(r, pbuf) - // In case the image is smaller pre-read block size, 1kb for now. - // Ever possible ? probably not in case a compression is needed! - ar.buf = pbuf[:nr] - if err == io.EOF { - // If we could not pre-read anything, we can't determine if - // it is a compressed file. - ar.src = io.MultiReader(bytes.NewReader(pbuf[:nr]), r) - return ar, ErrPreReadError - } - - // Try each supported compression type, return upon first match. - - // Try lz4. - // magic number error will be thrown if source is not a lz4 archive. - // e.g. "lz4: bad magic number". - if ok, err := lz4.ValidFrameHeader(ar.buf); err == nil && ok { - ar.src = lz4.NewReader(io.MultiReader(bytes.NewReader(ar.buf), r)) - return ar, nil - } - - // Try other archive types here, gzip, xz, etc when needed. - - // Last resort, read as is. - ar.src = io.MultiReader(bytes.NewReader(ar.buf), r) - return ar, nil -} - -func (ar ArchiveReader) Read(p []byte) (n int, err error) { - return ar.src.Read(p) -} diff --git a/pkg/uio/archivereader_test.go b/pkg/uio/archivereader_test.go deleted file mode 100644 index 798b1d172a..0000000000 --- a/pkg/uio/archivereader_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" - "math/rand" - "strings" - "testing" - "time" - - "github.com/pierrec/lz4/v4" -) - -const choices = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - -func TestArchiveReaderRegular(t *testing.T) { - dataStr := strings.Repeat("This is an important data!@#$%^^&&*&**(()())", 1000) - - ar, err := NewArchiveReader(bytes.NewReader([]byte(dataStr))) - if err != nil { - t.Fatalf("newArchiveReader(bytes.NewReader(%v)) returned error: %v", []byte(dataStr), err) - } - - buf := new(strings.Builder) - if _, err := io.Copy(buf, ar); err != nil { - t.Errorf("io.Copy(%v, %v) returned error: %v, want nil.", buf, ar, err) - } - if buf.String() != dataStr { - t.Errorf("got %s, want %s", buf.String(), dataStr) - } -} - -func TestArchiveReaderPreReadShort(t *testing.T) { - dataStr := "short data" - ar, err := NewArchiveReader(bytes.NewReader([]byte(dataStr))) - if err != nil { - t.Errorf("newArchiveReader(bytes.NewReader([]byte(%s))) returned err: %v, want nil", dataStr, err) - } - got, err := io.ReadAll(ar) - if err != nil { - t.Errorf("got error reading archive reader: %v, want nil", err) - } - if string(got) != dataStr { - t.Errorf("got %s, want %s", string(got), dataStr) - } - // Pre-read nothing. - dataStr = "" - ar, err = NewArchiveReader(bytes.NewReader([]byte(dataStr))) - if err != ErrPreReadError { - t.Errorf("newArchiveReader(bytes.NewReader([]byte(%s))) returned err: %v, want %v", dataStr, err, ErrPreReadError) - } - got, err = io.ReadAll(ar) - if err != nil { - t.Errorf("got error reading archive reader: %v, want nil", err) - } - if string(got) != dataStr { - t.Errorf("got %s, want %s", string(got), dataStr) - } -} - -// randomString generates random string of fixed length in a fast and simple way. -func randomString(l int) string { - rand.Seed(time.Now().UnixNano()) - r := make([]byte, l) - for i := 0; i < l; i++ { - r[i] = byte(choices[rand.Intn(len(choices))]) - } - return string(r) -} - -func checkArchiveReaderLZ4(t *testing.T, tt archiveReaderLZ4Case) { - t.Helper() - - srcR := bytes.NewReader([]byte(tt.dataStr)) - - srcBuf := new(bytes.Buffer) - lz4w := tt.setup(srcBuf) - - n, err := io.Copy(lz4w, srcR) - if err != nil { - t.Fatalf("io.Copy(%v, %v) returned error: %v, want nil", lz4w, srcR, err) - } - if n != int64(len([]byte(tt.dataStr))) { - t.Fatalf("got %d bytes compressed, want %d", n, len([]byte(tt.dataStr))) - } - if err = lz4w.Close(); err != nil { - t.Fatalf("Failed to close lz4 writer: %v", err) - } - - // Test ArchiveReader reading it. - ar, err := NewArchiveReader(bytes.NewReader(srcBuf.Bytes())) - if err != nil { - t.Fatalf("newArchiveReader(bytes.NewReader(%v)) returned error: %v", srcBuf.Bytes(), err) - } - buf := new(strings.Builder) - if _, err := io.Copy(buf, ar); err != nil { - t.Errorf("io.Copy(%v, %v) returned error: %v, want nil.", buf, ar, err) - } - if buf.String() != tt.dataStr { - t.Errorf("got %s, want %s", buf.String(), tt.dataStr) - } -} - -type archiveReaderLZ4Case struct { - name string - setup func(w io.Writer) *lz4.Writer - dataStr string -} - -func TestArchiveReaderLZ4(t *testing.T) { - for _, tt := range []archiveReaderLZ4Case{ - { - name: "non-legacy regular", - setup: func(w io.Writer) *lz4.Writer { - return lz4.NewWriter(w) - }, - dataStr: randomString(1024), - }, - { - name: "non-legacy larger data", - setup: func(w io.Writer) *lz4.Writer { - return lz4.NewWriter(w) - }, - dataStr: randomString(5 * 1024), - }, - { - name: "non-legacy short data", // Likley not realistic for most cases in the real world. - setup: func(w io.Writer) *lz4.Writer { - return lz4.NewWriter(w) - }, - dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes. - }, - { - name: "legacy regular", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(1024), - }, - { - name: "legacy larger data", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy small data", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes.. - }, - { - name: "legacy small data", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes.. - }, - { - name: "regular larger data with fast compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Fast)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy larger data with fast compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Fast)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - } { - t.Run(tt.name, func(t *testing.T) { - checkArchiveReaderLZ4(t, tt) - }) - } -} - -func TestArchiveReaderLZ4SlowCompressed(t *testing.T) { - for _, tt := range []archiveReaderLZ4Case{ - { - name: "regular larger data with medium compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level5)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "regular larger data with slow compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level9)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy larger data with medium compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level5)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy larger data with slow compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level9)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - } { - t.Run(tt.name, func(t *testing.T) { - checkArchiveReaderLZ4(t, tt) - }) - } -} diff --git a/pkg/uio/buffer.go b/pkg/uio/buffer.go deleted file mode 100644 index e7b39d2f84..0000000000 --- a/pkg/uio/buffer.go +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "encoding/binary" - "fmt" - - "github.com/u-root/u-root/pkg/align" - "github.com/u-root/u-root/pkg/ubinary" -) - -// Marshaler is the interface implemented by an object that can marshal itself -// into binary form. -// -// Marshal appends data to the buffer b. -type Marshaler interface { - Marshal(l *Lexer) -} - -// Unmarshaler is the interface implemented by an object that can unmarshal a -// binary representation of itself. -// -// Unmarshal Consumes data from the buffer b. -type Unmarshaler interface { - Unmarshal(l *Lexer) error -} - -// ToBytes marshals m in the given byte order. -func ToBytes(m Marshaler, order binary.ByteOrder) []byte { - l := NewLexer(NewBuffer(nil), order) - m.Marshal(l) - return l.Data() -} - -// FromBytes unmarshals b into obj in the given byte order. -func FromBytes(obj Unmarshaler, b []byte, order binary.ByteOrder) error { - l := NewLexer(NewBuffer(b), order) - return obj.Unmarshal(l) -} - -// ToBigEndian marshals m to big endian byte order. -func ToBigEndian(m Marshaler) []byte { - l := NewBigEndianBuffer(nil) - m.Marshal(l) - return l.Data() -} - -// FromBigEndian unmarshals b into obj in big endian byte order. -func FromBigEndian(obj Unmarshaler, b []byte) error { - l := NewBigEndianBuffer(b) - return obj.Unmarshal(l) -} - -// ToLittleEndian marshals m to little endian byte order. -func ToLittleEndian(m Marshaler) []byte { - l := NewLittleEndianBuffer(nil) - m.Marshal(l) - return l.Data() -} - -// FromLittleEndian unmarshals b into obj in little endian byte order. -func FromLittleEndian(obj Unmarshaler, b []byte) error { - l := NewLittleEndianBuffer(b) - return obj.Unmarshal(l) -} - -// Buffer implements functions to manipulate byte slices in a zero-copy way. -type Buffer struct { - // data is the underlying data. - data []byte - - // byteCount keeps track of how many bytes have been consumed for - // debugging. - byteCount int -} - -// NewBuffer Consumes b for marshaling or unmarshaling in the given byte order. -func NewBuffer(b []byte) *Buffer { - return &Buffer{data: b} -} - -// Preallocate increases the capacity of the buffer by n bytes. -func (b *Buffer) Preallocate(n int) { - b.data = append(b.data, make([]byte, 0, n)...) -} - -// WriteN appends n bytes to the Buffer and returns a slice pointing to the -// newly appended bytes. -func (b *Buffer) WriteN(n int) []byte { - b.data = append(b.data, make([]byte, n)...) - return b.data[len(b.data)-n:] -} - -// ReadN consumes n bytes from the Buffer. It returns nil, false if there -// aren't enough bytes left. -func (b *Buffer) ReadN(n int) ([]byte, error) { - if !b.Has(n) { - return nil, fmt.Errorf("buffer too short at position %d: have %d bytes, want %d bytes", b.byteCount, b.Len(), n) - } - rval := b.data[:n] - b.data = b.data[n:] - b.byteCount += n - return rval, nil -} - -// Data is unConsumed data remaining in the Buffer. -func (b *Buffer) Data() []byte { - return b.data -} - -// Has returns true if n bytes are available. -func (b *Buffer) Has(n int) bool { - return len(b.data) >= n -} - -// Len returns the length of the remaining bytes. -func (b *Buffer) Len() int { - return len(b.data) -} - -// Cap returns the available capacity. -func (b *Buffer) Cap() int { - return cap(b.data) -} - -// Lexer is a convenient encoder/decoder for buffers. -// -// Use: -// -// func (s *something) Unmarshal(l *Lexer) { -// s.Foo = l.Read8() -// s.Bar = l.Read8() -// s.Baz = l.Read16() -// return l.Error() -// } -type Lexer struct { - *Buffer - - // order is the byte order to write in / read in. - order binary.ByteOrder - - // err - err error -} - -// NewLexer returns a new coder for buffers. -func NewLexer(b *Buffer, order binary.ByteOrder) *Lexer { - return &Lexer{ - Buffer: b, - order: order, - } -} - -// NewLittleEndianBuffer returns a new little endian coder for a new buffer. -func NewLittleEndianBuffer(b []byte) *Lexer { - return &Lexer{ - Buffer: NewBuffer(b), - order: binary.LittleEndian, - } -} - -// NewBigEndianBuffer returns a new big endian coder for a new buffer. -func NewBigEndianBuffer(b []byte) *Lexer { - return &Lexer{ - Buffer: NewBuffer(b), - order: binary.BigEndian, - } -} - -// NewNativeEndianBuffer returns a new native endian coder for a new buffer. -func NewNativeEndianBuffer(b []byte) *Lexer { - return &Lexer{ - Buffer: NewBuffer(b), - order: ubinary.NativeEndian, - } -} - -func (l *Lexer) setError(err error) { - if l.err == nil { - l.err = err - } -} - -// Consume returns a slice of the next n bytes from the buffer. -// -// Consume gives direct access to the underlying data. -func (l *Lexer) Consume(n int) []byte { - v, err := l.Buffer.ReadN(n) - if err != nil { - l.setError(err) - return nil - } - return v -} - -func (l *Lexer) append(n int) []byte { - return l.Buffer.WriteN(n) -} - -// Error returns an error if an error occurred reading from the buffer. -func (l *Lexer) Error() error { - return l.err -} - -// FinError returns an error if an error occurred or if there is more data left -// to read in the buffer. -func (l *Lexer) FinError() error { - if l.err != nil { - return l.err - } - if l.Buffer.Len() > 0 { - return fmt.Errorf("buffer contains more bytes than it should") - } - return nil -} - -// Read8 reads a byte from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read8() uint8 { - v := l.Consume(1) - if v == nil { - return 0 - } - return uint8(v[0]) -} - -// Read16 reads a 16-bit value from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read16() uint16 { - v := l.Consume(2) - if v == nil { - return 0 - } - return l.order.Uint16(v) -} - -// Read32 reads a 32-bit value from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read32() uint32 { - v := l.Consume(4) - if v == nil { - return 0 - } - return l.order.Uint32(v) -} - -// Read64 reads a 64-bit value from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read64() uint64 { - v := l.Consume(8) - if v == nil { - return 0 - } - return l.order.Uint64(v) -} - -// CopyN returns a copy of the next n bytes. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) CopyN(n int) []byte { - v := l.Consume(n) - if v == nil { - return nil - } - - p := make([]byte, n) - m := copy(p, v) - return p[:m] -} - -// ReadAll Consumes and returns a copy of all remaining bytes in the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) ReadAll() []byte { - return l.CopyN(l.Len()) -} - -// ReadBytes reads exactly len(p) values from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) ReadBytes(p []byte) { - copy(p, l.Consume(len(p))) -} - -// Read implements io.Reader.Read. -func (l *Lexer) Read(p []byte) (int, error) { - v := l.Consume(len(p)) - if v == nil { - return 0, l.Error() - } - return copy(p, v), nil -} - -// ReadData reads the binary representation of data from the buffer. -// -// See binary.Read. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) ReadData(data interface{}) { - l.setError(binary.Read(l, l.order, data)) -} - -// WriteData writes a binary representation of data to the buffer. -// -// See binary.Write. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) WriteData(data interface{}) { - l.setError(binary.Write(l, l.order, data)) -} - -// Write8 writes a byte to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write8(v uint8) { - l.append(1)[0] = byte(v) -} - -// Write16 writes a 16-bit value to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write16(v uint16) { - l.order.PutUint16(l.append(2), v) -} - -// Write32 writes a 32-bit value to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write32(v uint32) { - l.order.PutUint32(l.append(4), v) -} - -// Write64 writes a 64-bit value to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write64(v uint64) { - l.order.PutUint64(l.append(8), v) -} - -// Append returns a newly appended n-size Buffer to write to. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Append(n int) []byte { - return l.append(n) -} - -// WriteBytes writes p to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) WriteBytes(p []byte) { - copy(l.append(len(p)), p) -} - -// Write implements io.Writer.Write. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write(p []byte) (int, error) { - return copy(l.append(len(p)), p), nil -} - -// Align appends bytes to align the length of the buffer to be divisible by n. -func (l *Lexer) Align(n int) { - pad := int(align.Up(uint(l.Len()), uint(n))) - l.Len() - l.Append(pad) -} diff --git a/pkg/uio/cached.go b/pkg/uio/cached.go deleted file mode 100644 index a39ff981ea..0000000000 --- a/pkg/uio/cached.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" -) - -// CachingReader is a lazily caching wrapper of an io.Reader. -// -// The wrapped io.Reader is only read from on demand, not upfront. -type CachingReader struct { - buf bytes.Buffer - r io.Reader - pos int - eof bool -} - -// NewCachingReader buffers reads from r. -// -// r is only read from when Read() is called. -func NewCachingReader(r io.Reader) *CachingReader { - return &CachingReader{ - r: r, - } -} - -func (cr *CachingReader) read(p []byte) (int, error) { - n, err := cr.r.Read(p) - cr.buf.Write(p[:n]) - if err == io.EOF || (n == 0 && err == nil) { - cr.eof = true - return n, io.EOF - } - return n, err -} - -// NewReader returns a new io.Reader that reads cr from offset 0. -func (cr *CachingReader) NewReader() io.Reader { - return Reader(cr) -} - -// Read reads from cr; implementing io.Reader. -// -// TODO(chrisko): Decide whether to keep this or only keep NewReader(). -func (cr *CachingReader) Read(p []byte) (int, error) { - n, err := cr.ReadAt(p, int64(cr.pos)) - cr.pos += n - return n, err -} - -// ReadAt reads from cr; implementing io.ReaderAt. -func (cr *CachingReader) ReadAt(p []byte, off int64) (int, error) { - if len(p) == 0 { - return 0, nil - } - end := int(off) + len(p) - - // Is the caller asking for some uncached bytes? - unread := end - cr.buf.Len() - if unread > 0 { - // Avoiding allocations: use `p` to read more bytes. - for unread > 0 { - toRead := unread % len(p) - if toRead == 0 { - toRead = len(p) - } - - m, err := cr.read(p[:toRead]) - unread -= m - if err == io.EOF { - break - } - if err != nil { - return 0, err - } - } - } - - // If this is true, the entire file was read just to find out, but the - // offset is beyond the end of the file. - if off > int64(cr.buf.Len()) { - return 0, io.EOF - } - - var err error - // Did the caller ask for more than was available? - // - // Note that any io.ReaderAt implementation *must* return an error for - // short reads. - if cr.eof && unread > 0 { - err = io.EOF - } - return copy(p, cr.buf.Bytes()[off:]), err -} diff --git a/pkg/uio/cached_test.go b/pkg/uio/cached_test.go deleted file mode 100644 index fda93df80d..0000000000 --- a/pkg/uio/cached_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "fmt" - "io" - "testing" -) - -func TestCachingReaderRead(t *testing.T) { - type read struct { - // Buffer sizes to call Read with. - size int - - // Buffer value corresponding Read(size) we want. - want []byte - - // Error corresponding to Read(size) we want. - err error - } - - for i, tt := range []struct { - // Content of the underlying io.Reader. - content []byte - - // Read calls to make in order. - reads []read - }{ - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []read{ - { - size: 0, - }, - { - size: 1, - want: []byte{0x11}, - }, - { - size: 2, - want: []byte{0x22, 0x33}, - }, - { - size: 0, - }, - { - size: 3, - want: []byte{0x44, 0x55, 0x66}, - }, - { - size: 4, - want: []byte{0x77, 0x88, 0x99}, - err: io.EOF, - }, - }, - }, - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []read{ - { - size: 11, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - err: io.EOF, - }, - }, - }, - { - content: nil, - reads: []read{ - { - size: 2, - err: io.EOF, - }, - { - size: 0, - }, - }, - }, - { - content: []byte{0x33, 0x22, 0x11}, - reads: []read{ - { - size: 3, - want: []byte{0x33, 0x22, 0x11}, - err: nil, - }, - { - size: 0, - }, - { - size: 1, - err: io.EOF, - }, - }, - }, - } { - t.Run(fmt.Sprintf("Test [%02d]", i), func(t *testing.T) { - buf := NewCachingReader(bytes.NewBuffer(tt.content)) - for j, r := range tt.reads { - p := make([]byte, r.size) - m, err := buf.Read(p) - if err != r.err { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, err, r.err) - } - if m != len(r.want) { - t.Errorf("Read#%d(%d) = %d, want %d", j, r.size, m, len(r.want)) - } - if !bytes.Equal(r.want, p[:m]) { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, p[:m], r.want) - } - } - }) - } -} - -func TestCachingReaderReadAt(t *testing.T) { - type readAt struct { - // Buffer sizes to call Read with. - size int - - // Offset to read from. - off int64 - - // Buffer value corresponding Read(size) we want. - want []byte - - // Error corresponding to Read(size) we want. - err error - } - - for i, tt := range []struct { - // Content of the underlying io.Reader. - content []byte - - // Read calls to make in order. - reads []readAt - }{ - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []readAt{ - { - off: 0, - size: 0, - }, - { - off: 0, - size: 1, - want: []byte{0x11}, - }, - { - off: 1, - size: 2, - want: []byte{0x22, 0x33}, - }, - { - off: 0, - size: 0, - }, - { - off: 3, - size: 3, - want: []byte{0x44, 0x55, 0x66}, - }, - { - off: 6, - size: 4, - want: []byte{0x77, 0x88, 0x99}, - err: io.EOF, - }, - { - off: 0, - size: 9, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - }, - { - off: 0, - size: 10, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - err: io.EOF, - }, - { - off: 0, - size: 8, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, - }, - }, - }, - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []readAt{ - { - off: 10, - size: 10, - err: io.EOF, - }, - { - off: 5, - size: 4, - want: []byte{0x66, 0x77, 0x88, 0x99}, - }, - }, - }, - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []readAt{ - { - size: 9, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - }, - { - off: 5, - size: 4, - want: []byte{0x66, 0x77, 0x88, 0x99}, - }, - { - off: 9, - size: 1, - err: io.EOF, - }, - }, - }, - } { - t.Run(fmt.Sprintf("Test [%02d]", i), func(t *testing.T) { - buf := NewCachingReader(bytes.NewBuffer(tt.content)) - for j, r := range tt.reads { - p := make([]byte, r.size) - m, err := buf.ReadAt(p, r.off) - if err != r.err { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, err, r.err) - } - if m != len(r.want) { - t.Errorf("Read#%d(%d) = %d, want %d", j, r.size, m, len(r.want)) - } - if !bytes.Equal(r.want, p[:m]) { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, p[:m], r.want) - } - } - }) - } -} diff --git a/pkg/uio/lazy.go b/pkg/uio/lazy.go deleted file mode 100644 index 4cb06ac32b..0000000000 --- a/pkg/uio/lazy.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "fmt" - "io" - "os" -) - -// ReadOneByte reads one byte from given io.ReaderAt. -func ReadOneByte(r io.ReaderAt) error { - buf := make([]byte, 1) - n, err := r.ReadAt(buf, 0) - if err != nil { - return err - } - if n != 1 { - return fmt.Errorf("expected to read 1 byte, but got %d", n) - } - return nil -} - -// LazyOpener is a lazy io.Reader. -// -// LazyOpener will use a given open function to derive an io.Reader when Read -// is first called on the LazyOpener. -type LazyOpener struct { - r io.Reader - s string - err error - open func() (io.Reader, error) -} - -// NewLazyOpener returns a lazy io.Reader based on `open`. -func NewLazyOpener(filename string, open func() (io.Reader, error)) *LazyOpener { - if len(filename) == 0 { - return nil - } - return &LazyOpener{s: filename, open: open} -} - -// Read implements io.Reader.Read lazily. -// -// If called for the first time, the underlying reader will be obtained and -// then used for the first and subsequent calls to Read. -func (lr *LazyOpener) Read(p []byte) (int, error) { - if lr.r == nil && lr.err == nil { - lr.r, lr.err = lr.open() - } - if lr.err != nil { - return 0, lr.err - } - return lr.r.Read(p) -} - -// String implements fmt.Stringer. -func (lr *LazyOpener) String() string { - if len(lr.s) > 0 { - return lr.s - } - if lr.r != nil { - return fmt.Sprintf("%v", lr.r) - } - return "unopened mystery file" -} - -// Close implements io.Closer.Close. -func (lr *LazyOpener) Close() error { - if c, ok := lr.r.(io.Closer); ok { - return c.Close() - } - return nil -} - -// LazyOpenerAt is a lazy io.ReaderAt. -// -// LazyOpenerAt will use a given open function to derive an io.ReaderAt when -// ReadAt is first called. -type LazyOpenerAt struct { - r io.ReaderAt - s string - err error - limit int64 - open func() (io.ReaderAt, error) -} - -// NewLazyFile returns a lazy ReaderAt opened from path. -func NewLazyFile(path string) *LazyOpenerAt { - if len(path) == 0 { - return nil - } - return NewLazyOpenerAt(path, func() (io.ReaderAt, error) { - return os.Open(path) - }) -} - -// NewLazyLimitFile returns a lazy ReaderAt opened from path with a limit reader on it. -func NewLazyLimitFile(path string, limit int64) *LazyOpenerAt { - if len(path) == 0 { - return nil - } - return NewLazyLimitOpenerAt(path, limit, func() (io.ReaderAt, error) { - return os.Open(path) - }) -} - -// NewLazyOpenerAt returns a lazy io.ReaderAt based on `open`. -func NewLazyOpenerAt(filename string, open func() (io.ReaderAt, error)) *LazyOpenerAt { - return &LazyOpenerAt{s: filename, open: open, limit: -1} -} - -// NewLazyLimitOpenerAt returns a lazy io.ReaderAt based on `open`. -func NewLazyLimitOpenerAt(filename string, limit int64, open func() (io.ReaderAt, error)) *LazyOpenerAt { - return &LazyOpenerAt{s: filename, open: open, limit: limit} -} - -// String implements fmt.Stringer. -func (loa *LazyOpenerAt) String() string { - if len(loa.s) > 0 { - return loa.s - } - if loa.r != nil { - return fmt.Sprintf("%v", loa.r) - } - return "unopened mystery file" -} - -// File returns the backend file of the io.ReaderAt if it -// is backed by a os.File. -func (loa *LazyOpenerAt) File() *os.File { - if f, ok := loa.r.(*os.File); ok { - return f - } - return nil -} - -// ReadAt implements io.ReaderAt.ReadAt. -func (loa *LazyOpenerAt) ReadAt(p []byte, off int64) (int, error) { - if loa.r == nil && loa.err == nil { - loa.r, loa.err = loa.open() - } - if loa.err != nil { - return 0, loa.err - } - if loa.limit > 0 { - if off >= loa.limit { - return 0, io.EOF - } - if int64(len(p)) > loa.limit-off { - p = p[0 : loa.limit-off] - } - } - return loa.r.ReadAt(p, off) -} - -// Close implements io.Closer.Close. -func (loa *LazyOpenerAt) Close() error { - if c, ok := loa.r.(io.Closer); ok { - return c.Close() - } - return nil -} diff --git a/pkg/uio/lazy_test.go b/pkg/uio/lazy_test.go deleted file mode 100644 index 5c83855cbe..0000000000 --- a/pkg/uio/lazy_test.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "errors" - "fmt" - "io" - "strings" - "testing" -) - -type mockReader struct { - // called is whether Read has been called. - called bool - - // err is the error to return on Read. - err error -} - -func (m *mockReader) Read([]byte) (int, error) { - m.called = true - return 0, m.err -} - -func (m *mockReader) ReadAt([]byte, int64) (int, error) { - m.called = true - return 0, m.err -} - -func TestLazyOpenerRead(t *testing.T) { - for i, tt := range []struct { - openErr error - reader *mockReader - wantCalled bool - }{ - { - openErr: nil, - reader: &mockReader{}, - wantCalled: true, - }, - { - openErr: io.EOF, - reader: nil, - wantCalled: false, - }, - { - openErr: nil, - reader: &mockReader{ - err: io.ErrUnexpectedEOF, - }, - wantCalled: true, - }, - } { - t.Run(fmt.Sprintf("Test #%02d", i), func(t *testing.T) { - var opened bool - lr := NewLazyOpener("testname", func() (io.Reader, error) { - opened = true - return tt.reader, tt.openErr - }) - _, err := lr.Read([]byte{}) - if !opened { - t.Fatalf("Read(): Reader was not opened") - } - if tt.openErr != nil && err != tt.openErr { - t.Errorf("Read() = %v, want %v", err, tt.openErr) - } - if tt.reader != nil { - if got, want := tt.reader.called, tt.wantCalled; got != want { - t.Errorf("mockReader.Read() called is %v, want %v", got, want) - } - if tt.reader.err != nil && err != tt.reader.err { - t.Errorf("Read() = %v, want %v", err, tt.reader.err) - } - } - }) - } -} - -func TestLazyOpenerReadAt(t *testing.T) { - for i, tt := range []struct { - limit int64 - bufSize int - openErr error - reader io.ReaderAt - off int64 - want error - wantB []byte - }{ - { - limit: -1, - bufSize: 10, - openErr: nil, - reader: &mockReader{}, - }, - { - limit: -1, - bufSize: 10, - openErr: io.EOF, - reader: nil, - want: io.EOF, - }, - { - limit: -1, - bufSize: 10, - openErr: nil, - reader: &mockReader{ - err: io.ErrUnexpectedEOF, - }, - want: io.ErrUnexpectedEOF, - }, - { - limit: -1, - bufSize: 6, - reader: strings.NewReader("foobar"), - wantB: []byte("foobar"), - }, - { - limit: -1, - off: 3, - bufSize: 3, - reader: strings.NewReader("foobar"), - wantB: []byte("bar"), - }, - { - limit: 5, - off: 3, - bufSize: 3, - reader: strings.NewReader("foobar"), - wantB: []byte("ba"), - }, - { - limit: 2, - bufSize: 2, - reader: strings.NewReader("foobar"), - wantB: []byte("fo"), - }, - { - limit: 2, - off: 2, - reader: strings.NewReader("foobar"), - want: io.EOF, - }, - } { - t.Run(fmt.Sprintf("Test #%02d", i), func(t *testing.T) { - var opened bool - lr := NewLazyLimitOpenerAt("", tt.limit, func() (io.ReaderAt, error) { - opened = true - return tt.reader, tt.openErr - }) - - b := make([]byte, tt.bufSize) - n, err := lr.ReadAt(b, tt.off) - if !opened { - t.Fatalf("Read(): Reader was not opened") - } - if !errors.Is(tt.want, err) { - t.Errorf("Read() = %v, want %v", err, tt.want) - } - - if err == nil { - if !bytes.Equal(b[:n], tt.wantB) { - t.Errorf("Read() = %s, want %s", b[:n], tt.wantB) - } - } - }) - } -} diff --git a/pkg/uio/linewriter.go b/pkg/uio/linewriter.go deleted file mode 100644 index a78835b91c..0000000000 --- a/pkg/uio/linewriter.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" -) - -// LineWriter processes one line of log output at a time. -type LineWriter interface { - // OneLine is always called with exactly one line of output. - OneLine(b []byte) -} - -// FullLineWriter returns an io.Writer that waits for a full line of prints -// before calling w.Write on one line each. -func FullLineWriter(w LineWriter) io.WriteCloser { - return &fullLineWriter{w: w} -} - -type fullLineWriter struct { - w LineWriter - buffer []byte -} - -func (fsw *fullLineWriter) printBuf() { - bufs := bytes.Split(fsw.buffer, []byte{'\n'}) - for _, buf := range bufs { - if len(buf) != 0 { - fsw.w.OneLine(buf) - } - } - fsw.buffer = nil -} - -// Write implements io.Writer and buffers p until at least one full line is -// received. -func (fsw *fullLineWriter) Write(p []byte) (int, error) { - i := bytes.LastIndexByte(p, '\n') - if i == -1 { - fsw.buffer = append(fsw.buffer, p...) - } else { - fsw.buffer = append(fsw.buffer, p[:i]...) - fsw.printBuf() - fsw.buffer = append([]byte{}, p[i:]...) - } - return len(p), nil -} - -// Close implements io.Closer and flushes the buffer. -func (fsw *fullLineWriter) Close() error { - fsw.printBuf() - return nil -} diff --git a/pkg/uio/null.go b/pkg/uio/null.go deleted file mode 100644 index 64156f4c0e..0000000000 --- a/pkg/uio/null.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2012-2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Discard implementation copied from the Go project: -// https://golang.org/src/io/ioutil/ioutil.go. -// Copyright 2009 The Go Authors. All rights reserved. - -package uio - -import ( - "io" - "sync" -) - -type devNull int - -// devNull implements ReaderFrom as an optimization so io.Copy to -// ioutil.Discard can avoid doing unnecessary work. -var _ io.ReaderFrom = devNull(0) - -func (devNull) Write(p []byte) (int, error) { - return len(p), nil -} - -func (devNull) Name() string { - return "null" -} - -func (devNull) WriteString(s string) (int, error) { - return len(s), nil -} - -var blackHolePool = sync.Pool{ - New: func() interface{} { - b := make([]byte, 8192) - return &b - }, -} - -func (devNull) ReadFrom(r io.Reader) (n int64, err error) { - bufp := blackHolePool.Get().(*[]byte) - readSize := 0 - for { - readSize, err = r.Read(*bufp) - n += int64(readSize) - if err != nil { - blackHolePool.Put(bufp) - if err == io.EOF { - return n, nil - } - return - } - } -} - -func (devNull) Close() error { - return nil -} - -// WriteNameCloser is the interface that groups Write, Close, and Name methods. -type WriteNameCloser interface { - io.Writer - io.Closer - Name() string -} - -// Discard is a WriteNameCloser on which all Write and Close calls succeed -// without doing anything, and the Name call returns "null". -var Discard WriteNameCloser = devNull(0) diff --git a/pkg/uio/progress.go b/pkg/uio/progress.go deleted file mode 100644 index 606b1eabe9..0000000000 --- a/pkg/uio/progress.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "io" - "strings" -) - -// ProgressReadCloser implements io.ReadCloser and prints Symbol to W after every -// Interval bytes passes through RC. -type ProgressReadCloser struct { - RC io.ReadCloser - - Symbol string - Interval int - W io.Writer - - counter int - written bool -} - -// Read implements io.Reader for ProgressReadCloser. -func (rc *ProgressReadCloser) Read(p []byte) (n int, err error) { - defer func() { - numSymbols := (rc.counter%rc.Interval + n) / rc.Interval - rc.W.Write([]byte(strings.Repeat(rc.Symbol, numSymbols))) - rc.counter += n - rc.written = (rc.written || numSymbols > 0) - if err == io.EOF && rc.written { - rc.W.Write([]byte("\n")) - } - }() - return rc.RC.Read(p) -} - -// Read implements io.Closer for ProgressReader. -func (rc *ProgressReadCloser) Close() error { - return rc.RC.Close() -} diff --git a/pkg/uio/progress_test.go b/pkg/uio/progress_test.go deleted file mode 100644 index 6d8de14ecf..0000000000 --- a/pkg/uio/progress_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" - "testing" -) - -func TestProgressReadCloser(t *testing.T) { - input := io.NopCloser(bytes.NewBufferString("01234567890123456789")) - stdout := &bytes.Buffer{} - prc := ProgressReadCloser{ - RC: input, - Symbol: "#", - Interval: 4, - W: stdout, - } - - // Read one byte at a time. - output := make([]byte, 1) - prc.Read(output) - prc.Read(output) - prc.Read(output) - if len(stdout.Bytes()) != 0 { - t.Errorf("found %q, but expected no bytes to be written", stdout) - } - prc.Read(output) - if stdout.String() != "#" { - t.Errorf("found %q, expected %q to be written", stdout.String(), "#") - } - - // Read 9 bytes all at once. - output = make([]byte, 9) - prc.Read(output) - if stdout.String() != "###" { - t.Errorf("found %q, expected %q to be written", stdout.String(), "###") - } - if string(output) != "456789012" { - t.Errorf("found %q, expected %q to be written", string(output), "456789012") - } - - // Read until EOF - output, err := io.ReadAll(&prc) - if err != nil { - t.Errorf("got %v, expected nil error", err) - } - if stdout.String() != "#####\n" { - t.Errorf("found %q, expected %q to be written", stdout.String(), "#####\n") - } - if string(output) != "3456789" { - t.Errorf("found %q, expected %q to be written", string(output), "3456789") - } - - err = prc.Close() - if err != nil { - t.Errorf("got %v, expected nil error", err) - } -} diff --git a/pkg/uio/reader.go b/pkg/uio/reader.go deleted file mode 100644 index 0ca839a073..0000000000 --- a/pkg/uio/reader.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" - "math" - "os" - "reflect" -) - -type inMemReaderAt interface { - Bytes() []byte -} - -// ReadAll reads everything that r contains. -// -// Callers *must* not modify bytes in the returned byte slice. -// -// If r is an in-memory representation, ReadAll will attempt to return a -// pointer to those bytes directly. -func ReadAll(r io.ReaderAt) ([]byte, error) { - if imra, ok := r.(inMemReaderAt); ok { - return imra.Bytes(), nil - } - return io.ReadAll(Reader(r)) -} - -// Reader generates a Reader from a ReaderAt. -func Reader(r io.ReaderAt) io.Reader { - return io.NewSectionReader(r, 0, math.MaxInt64) -} - -// ReaderAtEqual compares the contents of r1 and r2. -func ReaderAtEqual(r1, r2 io.ReaderAt) bool { - var c, d []byte - var r1err, r2err error - if r1 != nil { - c, r1err = ReadAll(r1) - } - if r2 != nil { - d, r2err = ReadAll(r2) - } - return bytes.Equal(c, d) && reflect.DeepEqual(r1err, r2err) -} - -// ReadIntoFile reads all from io.Reader into the file at given path. -// -// If the file at given path does not exist, a new file will be created. -// If the file exists at the given path, but not empty, it will be truncated. -func ReadIntoFile(r io.Reader, p string) error { - f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o644) - if err != nil { - return err - } - defer f.Close() - - _, err = io.Copy(f, r) - if err != nil { - return err - } - - return f.Close() -} diff --git a/pkg/uio/reader_test.go b/pkg/uio/reader_test.go deleted file mode 100644 index c6ebfc70bd..0000000000 --- a/pkg/uio/reader_test.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func readAndCheck(t *testing.T, want, tmpfileP string) { - t.Helper() - r := strings.NewReader(want) - if err := ReadIntoFile(r, tmpfileP); err != nil { - t.Errorf("ReadIntoFile(%v, %s) = %v, want no error", r, tmpfileP, err) - } - - got, err := os.ReadFile(tmpfileP) - if err != nil { - t.Fatalf("os.ReadFile(%s) = %v, want no error", tmpfileP, err) - } - if want != string(got) { - t.Errorf("got: %v, want %s", string(got), want) - } -} - -func TestReadIntoFile(t *testing.T) { - want := "I am the wanted" - - dir := t.TempDir() - - // Write to a file already exist. - p := filepath.Join(dir, "uio-out") - // Expect net effect to be creating a new empty file: "uio-out". - f, err := os.OpenFile(p, os.O_RDONLY|os.O_CREATE|os.O_TRUNC, 0o755) - if err != nil { - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } - readAndCheck(t, want, f.Name()) - - // Write to a file that does not exist. - p = filepath.Join(dir, "uio-out-not-existing") - readAndCheck(t, want, p) - - // Write to an existing file that has pre-existing content. - p = filepath.Join(dir, "uio-out-prexist-content") - f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755) - if err != nil { - t.Fatal(err) - } - if _, err := f.Write([]byte("temporary file's content")); err != nil { - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } - readAndCheck(t, want, p) -} diff --git a/pkg/uio/uio.go b/pkg/uio/uio.go deleted file mode 100644 index bdd507c8eb..0000000000 --- a/pkg/uio/uio.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package uio unifies commonly used io utilities for u-root. -// -// uio's most used feature is the Buffer/Lexer combination to parse binary data -// of arbitrary endianness into data structures. -package uio diff --git a/pkg/uio/uiotest/pkg_test.go b/pkg/uio/uiotest/pkg_test.go deleted file mode 100644 index cbe3021508..0000000000 --- a/pkg/uio/uiotest/pkg_test.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uiotest - -import "testing" - -func TestTODO(t *testing.T) { - // TODO: Write a unit test. -} diff --git a/pkg/uroot/initramfs/files_test.go b/pkg/uroot/initramfs/files_test.go index c25099e32b..66746f6b1e 100644 --- a/pkg/uroot/initramfs/files_test.go +++ b/pkg/uroot/initramfs/files_test.go @@ -14,7 +14,7 @@ import ( "testing" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func TestFilesAddFileNoFollow(t *testing.T) { diff --git a/pkg/uroot/initramfs/test/ramfs.go b/pkg/uroot/initramfs/test/ramfs.go index 47fe904a88..ce0b8d5fea 100644 --- a/pkg/uroot/initramfs/test/ramfs.go +++ b/pkg/uroot/initramfs/test/ramfs.go @@ -9,7 +9,7 @@ import ( "os" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type ArchiveValidator interface { diff --git a/pkg/vfile/vfile.go b/pkg/vfile/vfile.go index d54849e6e7..a7af7f33df 100644 --- a/pkg/vfile/vfile.go +++ b/pkg/vfile/vfile.go @@ -4,7 +4,7 @@ // Package vfile verifies files against a hash or signature. // -// vfile aims to be TOCTTOU-safe by reading files into memory before verifying. +// vfile is not TOCTTOU-safe against the contents of the file changing. package vfile import ( @@ -110,22 +110,10 @@ func GetRSAKeysFromRing(ring openpgp.KeyRing) ([]*rsa.PublicKey, error) { // OpenSignedSigFile calls OpenSignedFile expecting the signature to be in path.sig. // // E.g. if path is /foo/bar, the signature is expected to be in /foo/bar.sig. -func OpenSignedSigFile(keyring openpgp.KeyRing, path string, opts ...OpenSignedFileOption) (*File, error) { +func OpenSignedSigFile(keyring openpgp.KeyRing, path string, opts ...OpenSignedFileOption) (*os.File, error) { return OpenSignedFile(keyring, path, fmt.Sprintf("%s.sig", path), opts...) } -// File encapsulates a bytes.Reader with the file contents and its name. -type File struct { - *bytes.Reader - - FileName string -} - -// Name returns the file name. -func (f *File) Name() string { - return f.FileName -} - // OpenSignedFileOption is an optional argument to OpenSignedFile. type OpenSignedFileOption func(*openSignedFileOptions) @@ -151,19 +139,15 @@ func getEndOfTime() time.Time { // WARNING! Unlike many Go functions, this may return both the file and an // error. // -// It expects path.sig to be available. +// It expects pathSig to be available. // // If the signature does not exist or does not match the keyring, both the file // and a signature error will be returned. -func OpenSignedFile(keyring openpgp.KeyRing, path, pathSig string, opts ...OpenSignedFileOption) (*File, error) { - content, err := os.ReadFile(path) +func OpenSignedFile(keyring openpgp.KeyRing, path, pathSig string, opts ...OpenSignedFileOption) (*os.File, error) { + f, err := os.Open(path) if err != nil { return nil, err } - f := &File{ - Reader: bytes.NewReader(content), - FileName: path, - } var o openSignedFileOptions // Apply options if given. for _, opt := range opts { @@ -180,9 +164,13 @@ func OpenSignedFile(keyring openpgp.KeyRing, path, pathSig string, opts ...OpenS if o.ignoreTimeConflict { config.Time = getEndOfTime } + + // After CheckDetachedSignature reads the whole file, seek back to the beginning. + defer f.Seek(0, os.SEEK_SET) + if keyring == nil { return f, ErrUnsigned{Path: path, Err: ErrNoKeyRing} - } else if signer, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(content), signaturef, &config); err != nil { + } else if signer, err := openpgp.CheckDetachedSignature(keyring, f, signaturef, &config); err != nil { return f, ErrUnsigned{Path: path, Err: err} } else if signer == nil { return f, ErrUnsigned{Path: path, Err: ErrWrongSigner{keyring}} @@ -226,8 +214,8 @@ var ErrNoExpectedHash = errors.New("OpenHashedFile: no expected hash given") // WARNING! Unlike many Go functions, this may return both the file and an // error in case the expected hash does not match the contents. // -// If the contents match, the contents are returned with no error. -func OpenHashedFile256(path string, wantSHA256Hash []byte) (*File, error) { +// If the contents match, the opened file is returned with no error. +func OpenHashedFile256(path string, wantSHA256Hash []byte) (*os.File, error) { return openHashedFile(path, wantSHA256Hash, sha256.New()) } @@ -237,20 +225,16 @@ func OpenHashedFile256(path string, wantSHA256Hash []byte) (*File, error) { // WARNING! Unlike many Go functions, this may return both the file and an // error in case the expected hash does not match the contents. // -// If the contents match, the contents are returned with no error. -func OpenHashedFile512(path string, wantSHA512Hash []byte) (*File, error) { +// If the contents match, the opened file is returned with no error. +func OpenHashedFile512(path string, wantSHA512Hash []byte) (*os.File, error) { return openHashedFile(path, wantSHA512Hash, sha512.New()) } -func openHashedFile(path string, wantHash []byte, h hash.Hash) (*File, error) { - content, err := os.ReadFile(path) +func openHashedFile(path string, wantHash []byte, h hash.Hash) (*os.File, error) { + f, err := os.Open(path) if err != nil { return nil, err } - f := &File{ - Reader: bytes.NewReader(content), - FileName: path, - } if len(wantHash) == 0 { return f, ErrInvalidHash{ @@ -259,8 +243,11 @@ func openHashedFile(path string, wantHash []byte, h hash.Hash) (*File, error) { } } + // After io.Copy reads the whole file, Seek back to beginning. + defer f.Seek(0, os.SEEK_SET) + // Hash the file. - if _, err := io.Copy(h, bytes.NewReader(content)); err != nil { + if _, err := io.Copy(h, f); err != nil { return f, ErrInvalidHash{ Path: path, Err: err, diff --git a/templates.go b/templates.go index c668ea1d2e..a517e67097 100644 --- a/templates.go +++ b/templates.go @@ -38,9 +38,9 @@ var templates = map[string][]string{ "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/dmesg", "github.com/u-root/u-root/cmds/core/echo", - "github.com/u-root/u-root/cmds/core/elvish", "github.com/u-root/u-root/cmds/core/find", "github.com/u-root/u-root/cmds/core/free", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/u-root/u-root/cmds/core/gpgv", "github.com/u-root/u-root/cmds/core/grep", "github.com/u-root/u-root/cmds/core/gzip", @@ -91,8 +91,8 @@ var templates = map[string][]string{ "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/dmesg", "github.com/u-root/u-root/cmds/core/echo", - "github.com/u-root/u-root/cmds/core/elvish", "github.com/u-root/u-root/cmds/core/free", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/u-root/u-root/cmds/core/grep", "github.com/u-root/u-root/cmds/core/init", "github.com/u-root/u-root/cmds/core/insmod", @@ -121,8 +121,8 @@ var templates = map[string][]string{ "github.com/u-root/u-root/cmds/core/dd", "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/dmesg", - "github.com/u-root/u-root/cmds/core/elvish", "github.com/u-root/u-root/cmds/core/find", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/u-root/u-root/cmds/core/grep", "github.com/u-root/u-root/cmds/core/id", "github.com/u-root/u-root/cmds/core/init", diff --git a/u-root.go b/u-root.go index 89d33de985..7b5d38e3c0 100644 --- a/u-root.go +++ b/u-root.go @@ -68,7 +68,7 @@ func init() { case "plan9": sh = "" default: - sh = "elvish" + sh = "gosh" } build = flag.String("build", "gbb", "u-root build format (e.g. bb/gbb or binary).") diff --git a/uroot_test.go b/uroot_test.go index 85674289d0..cadc8fc546 100644 --- a/uroot_test.go +++ b/uroot_test.go @@ -20,8 +20,8 @@ import ( gbbgolang "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/cpio" "github.com/u-root/u-root/pkg/testutil" - "github.com/u-root/u-root/pkg/uio" itest "github.com/u-root/u-root/pkg/uroot/initramfs/test" + "github.com/u-root/uio/uio" ) var twocmds = []string{ diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go index 8cd4e333b9..83d7cdadd3 100644 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go +++ b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go @@ -9,7 +9,7 @@ import ( fp "github.com/cloudflare/circl/math/fp448" ) -// twistCurve is -x^2+y^2=1-39082x^2y^2 and is 4-isogeneous to Goldilocks. +// twistCurve is -x^2+y^2=1-39082x^2y^2 and is 4-isogenous to Goldilocks. type twistCurve struct{} // Identity returns the identity point. diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go b/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go index ab19d0ad12..1755fd1e6d 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go @@ -6,13 +6,21 @@ package sha3 // KeccakF1600 applies the Keccak permutation to a 1600b-wide // state represented as a slice of 25 uint64s. +// If turbo is true, applies the 12-round variant instead of the +// regular 24-round variant. // nolint:funlen -func KeccakF1600(a *[25]uint64) { +func KeccakF1600(a *[25]uint64, turbo bool) { // Implementation translated from Keccak-inplace.c // in the keccak reference code. var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 - for i := 0; i < 24; i += 4 { + i := 0 + + if turbo { + i = 12 + } + + for ; i < 24; i += 4 { // Combines the 5 steps in each round into 2 steps. // Unrolls 4 rounds per loop and spreads some steps across rounds. diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go b/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go index b35cd006b0..a0df5aa6c5 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go @@ -51,6 +51,7 @@ type State struct { // Specific to SHA-3 and SHAKE. outputLen int // the default output size in bytes state spongeDirection // whether the sponge is absorbing or squeezing + turbo bool // Whether we're using 12 rounds instead of 24 } // BlockSize returns the rate of sponge underlying this hash function. @@ -86,11 +87,11 @@ func (d *State) permute() { xorIn(d, d.buf()) d.bufe = 0 d.bufo = 0 - KeccakF1600(&d.a) + KeccakF1600(&d.a, d.turbo) case spongeSqueezing: // If we're squeezing, we need to apply the permutation before // copying more output. - KeccakF1600(&d.a) + KeccakF1600(&d.a, d.turbo) d.bufe = d.rate d.bufo = 0 copyOut(d, d.buf()) @@ -136,7 +137,7 @@ func (d *State) Write(p []byte) (written int, err error) { // The fast path; absorb a full "rate" bytes of input and apply the permutation. xorIn(d, p[:d.rate]) p = p[d.rate:] - KeccakF1600(&d.a) + KeccakF1600(&d.a, d.turbo) } else { // The slow path; buffer the input until we can fill the sponge, and then xor it in. todo := d.rate - bufl @@ -193,3 +194,7 @@ func (d *State) Sum(in []byte) []byte { _, _ = dup.Read(hash) return append(in, hash...) } + +func (d *State) IsAbsorbing() bool { + return d.state == spongeAbsorbing +} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/shake.go b/vendor/github.com/cloudflare/circl/internal/sha3/shake.go index b92c5b7d78..77817f758c 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/shake.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/shake.go @@ -57,6 +57,17 @@ func NewShake128() State { return State{rate: rate128, dsbyte: dsbyteShake} } +// NewTurboShake128 creates a new TurboSHAKE128 variable-output-length ShakeHash. +// Its generic security strength is 128 bits against all attacks if at +// least 32 bytes of its output are used. +// D is the domain separation byte and must be between 0x01 and 0x7f inclusive. +func NewTurboShake128(D byte) State { + if D == 0 || D > 0x7f { + panic("turboshake: D out of range") + } + return State{rate: rate128, dsbyte: D, turbo: true} +} + // NewShake256 creates a new SHAKE256 variable-output-length ShakeHash. // Its generic security strength is 256 bits against all attacks if // at least 64 bytes of its output are used. @@ -64,6 +75,17 @@ func NewShake256() State { return State{rate: rate256, dsbyte: dsbyteShake} } +// NewTurboShake256 creates a new TurboSHAKE256 variable-output-length ShakeHash. +// Its generic security strength is 256 bits against all attacks if +// at least 64 bytes of its output are used. +// D is the domain separation byte and must be between 0x01 and 0x7f inclusive. +func NewTurboShake256(D byte) State { + if D == 0 || D > 0x7f { + panic("turboshake: D out of range") + } + return State{rate: rate256, dsbyte: D, turbo: true} +} + // ShakeSum128 writes an arbitrary-length digest of data into hash. func ShakeSum128(hash, data []byte) { h := NewShake128() @@ -77,3 +99,21 @@ func ShakeSum256(hash, data []byte) { _, _ = h.Write(data) _, _ = h.Read(hash) } + +// TurboShakeSum128 writes an arbitrary-length digest of data into hash. +func TurboShakeSum128(hash, data []byte, D byte) { + h := NewTurboShake128(D) + _, _ = h.Write(data) + _, _ = h.Read(hash) +} + +// TurboShakeSum256 writes an arbitrary-length digest of data into hash. +func TurboShakeSum256(hash, data []byte, D byte) { + h := NewTurboShake256(D) + _, _ = h.Write(data) + _, _ = h.Read(hash) +} + +func (d *State) SwitchDS(D byte) { + d.dsbyte = D +} diff --git a/vendor/github.com/cloudflare/circl/math/primes.go b/vendor/github.com/cloudflare/circl/math/primes.go new file mode 100644 index 0000000000..158fd83a7a --- /dev/null +++ b/vendor/github.com/cloudflare/circl/math/primes.go @@ -0,0 +1,34 @@ +package math + +import ( + "crypto/rand" + "io" + "math/big" +) + +// IsSafePrime reports whether p is (probably) a safe prime. +// The prime p=2*q+1 is safe prime if both p and q are primes. +// Note that ProbablyPrime is not suitable for judging primes +// that an adversary may have crafted to fool the test. +func IsSafePrime(p *big.Int) bool { + pdiv2 := new(big.Int).Rsh(p, 1) + return p.ProbablyPrime(20) && pdiv2.ProbablyPrime(20) +} + +// SafePrime returns a number of the given bit length that is a safe prime with high probability. +// The number returned p=2*q+1 is a safe prime if both p and q are primes. +// SafePrime will return error for any error returned by rand.Read or if bits < 2. +func SafePrime(random io.Reader, bits int) (*big.Int, error) { + one := big.NewInt(1) + p := new(big.Int) + for { + q, err := rand.Prime(random, bits-1) + if err != nil { + return nil, err + } + p.Lsh(q, 1).Add(p, one) + if p.ProbablyPrime(20) { + return p, nil + } + } +} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go b/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go index 08ca65d799..2c73c26fb1 100644 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go +++ b/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go @@ -1,7 +1,7 @@ // Package ed25519 implements Ed25519 signature scheme as described in RFC-8032. // // This package provides optimized implementations of the three signature -// variants and maintaining closer compatiblilty with crypto/ed25519. +// variants and maintaining closer compatibility with crypto/ed25519. // // | Scheme Name | Sign Function | Verification | Context | // |-------------|-------------------|---------------|-------------------| diff --git a/vendor/github.com/hugelgupf/vmtest/README.md b/vendor/github.com/hugelgupf/vmtest/README.md index 1ec85c44de..d8a3f0e5b4 100644 --- a/vendor/github.com/hugelgupf/vmtest/README.md +++ b/vendor/github.com/hugelgupf/vmtest/README.md @@ -1,10 +1,94 @@ ## vmtest -[![CircleCI](https://circleci.com/gh/hugelgupf/vmtest.svg?style=svg)](https://circleci.com/gh/hugelgupf/vmtest) [![Go Report Card](https://goreportcard.com/badge/github.com/hugelgupf/vmtest)](https://goreportcard.com/report/github.com/hugelgupf/vmtest) [![GoDoc](https://godoc.org/github.com/hugelgupf/vmtest?status.svg)](https://godoc.org/github.com/hugelgupf/vmtest) -Fun stuff coming +vmtest is a Go API for launching QEMU VMs. + +* [The `qemu` package](https://pkg.go.dev/github.com/hugelgupf/vmtest/qemu) + contains APIs for + + * launching QEMU processes + * configuring QEMU devices (such as a shared 9P directory, networking, + serial logging, etc) + * running tasks (goroutines) bound to the VM process lifetime, and + * using expect-scripting to check for outputs. + +* [The `uqemu` package](https://pkg.go.dev/github.com/hugelgupf/vmtest/uqemu) + can be used to configure a u-root initramfs to be used as the boot root file + system. + +* [The `vmtest` package](https://pkg.go.dev/github.com/hugelgupf/vmtest) + contains + + * a `testing.TB` wrapper around the `qemu` API with some safe defaults + (logging serial console to `t.Logf`, etc) + * an API for running shell scripts in the guest + * an API for running Go unit tests in the guest and collecting their + results. + +Out of these, the `vmtest` API is still the most raw and being iterated on. + +## Running Tests + +The `qemu` API picks up the following values from env vars by default: + +* `VMTEST_QEMU`: QEMU binary + arguments (e.g. + `VMTEST_QEMU="qemu-system-x86_64 -enable-kvm"`) +* `VMTEST_KERNEL`: Kernel to boot. +* `VMTEST_ARCH`: Guest architecture (same as GOARCH values). Must match the QEMU + binary supplied. If not supplied, defaults to `runtime.GOARCH`, i.e. it + matches the host's GOARCH. +* `VMTEST_KERNEL_APPEND`: is added to kernel command-line arguments +* `VMTEST_QEMU_APPEND`: is added to QEMU command-line arguments. +* `VMTEST_TIMEOUT`: Timeout value (e.g. `1m20s` -- parsed by Go's + `time.ParseDuration`). +* `VMTEST_INITRAMFS`: Initramfs to boot. + +Most of these values can be overriden in the Go API, but typically only +`VMTEST_INITRAMFS` and `VMTEST_TIMEOUT` are. `VMTEST_KERNEL_APPEND` and +`VMTEST_QEMU_APPEND` are always additive. + +The `runvmtest` tool automatically downloads `VMTEST_QEMU` and +`VMTEST_KERNEL` for use with tests based on a provided `VMTEST_ARCH`. E.g. + +```sh +go install github.com/hugelgupf/vmtest/tools/runvmtest@latest + +# See how it works: +runvmtest -- bash -c "echo \$VMTEST_KERNEL -- \$VMTEST_QEMU" + +# Intended usage: +runvmtest -- go test -v ./tests/gohello + +# Or run an Arm64 guest: +VMTEST_ARCH=arm64 runvmtest -- go test -v ./tests/gohello +``` + +You can also override one or both, which will just be passed through: + +```sh +# Will only download VMTEST_KERNEL. +VMTEST_ARCH=arm64 VMTEST_QEMU="qemu-system-aarch64 -enable-kvm" runvmtest -- go test -v ./tests/gohello +``` + +To keep the artifacts around locally to reproduce the same test: + +```s +runvmtest --keep-artifacts -- go test -v ./tests/gohello +``` + +The default kernel and QEMU supplied by `runvmtest` may of course not work well +for your tests. You can configure `runvmtest` to supply your own `VMTEST_KERNEL` +and `VMTEST_QEMU` -- but also any additional environment variables. See +[`runvmtest` configuration](#custom-runvmtest-configuration). + +To build your own kernel or QEMU, check out +[images/kernel-arm64](./images/kernel-arm64) for building a kernel-image-only +Docker image, and [images/qemu](./images/qemu/Dockerfile) for how we build a +Docker image with just QEMU binaries and their dependencies. + +## Writing Tests ### Example: qemu API @@ -25,7 +109,7 @@ func TestStartVM(t *testing.T) { qemu.WithInitramfs("./somewhere.cpio"), qemu.WithAppendKernel("console=ttyS0 earlyprintk=ttyS0"), - qemu.LogSerialByLine(qemu.PrintLineWithPrefix("vm", t.Logf)), + qemu.LogSerialByLine(qemu.DefaultPrint("vm", t.Logf)), ) if err != nil { t.Fatalf("Failed to start VM: %v", err) @@ -46,7 +130,6 @@ func TestStartVM(t *testing.T) { ```go func TestStartVM(t *testing.T) { - l := &ulogtest.Logger{TB: t} initramfs := uroot.Opts{ TempDir: t.TempDir(), InitCmd: "init", @@ -62,7 +145,7 @@ func TestStartVM(t *testing.T) { } vm, err := qemu.Start( qemu.ArchUseEnvv, - uqemu.WithUrootInitramfs(l, initramfs, filepath.Join(t.TempDir(), "initramfs.cpio")), + uqemu.WithUrootInitramfsT(t, initramfs), // Other options... ) @@ -87,8 +170,50 @@ func TestStartVM(t *testing.T) { return exec.CommandContext(ctx, "sleep", "900").Run() }, ), + + // Task that runs when the VM exits. + qemu.WithTask(qemu.Cleanup(func() error { + // Do something. + return fmt.Errorf("this is returned by vm.Wait()") + })), + + // Task that only runs when VM starts. + qemu.WithTask(qemu.WaitVMStarted(...)), ) // ... } ``` +### Example: vmtest API + +See [tests/startexample](./tests/startexample/vm_test.go) + +### Example: Go unit tests in VM + +See [tests/gobench](./tests/gobench/bench_test.go) + +## Custom runvmtest configuration + +`runvmtest` tries to read a config from `.vmtest.yaml` in the current working +directory or any of its parents. + +Given this is a Go-based test framework, the recommendation would be to place +`.vmtest.yaml` in the same directory as your `go.mod` so that the config is +available anywhere `go test` is for that module. + +`runvmtest` can be configured to set up any number of environment variables. +Config format looks like this: + +``` +VMTEST_ARCH: + ENV_VAR: + container: + template: "{{.somedir}} -foobar {{.somefile}}" + files: + somefile: + directories: + somedir: +``` + +Check out the example in +[tools/runvmtest/example-vmtest.yaml](./tools/runvmtest/example-vmtest.yaml). diff --git a/vendor/github.com/hugelgupf/vmtest/coverage.go b/vendor/github.com/hugelgupf/vmtest/coverage.go index 7aa971c426..b2bfe12a54 100644 --- a/vendor/github.com/hugelgupf/vmtest/coverage.go +++ b/vendor/github.com/hugelgupf/vmtest/coverage.go @@ -14,6 +14,24 @@ import ( "github.com/hugelgupf/vmtest/testtmp" ) +// ShareGOCOVERDIR shares VMTEST_GOCOVERDIR with the guest if it's available in the +// environment. +// +// Call guest.GOCOVERDIR to set up the directory in the guest. +func ShareGOCOVERDIR() Opt { + return func(t testing.TB, v *VMOptions) error { + goCov := os.Getenv("VMTEST_GOCOVERDIR") + if goCov == "" { + return nil + } + v.QEMUOpts = append(v.QEMUOpts, + qemu.P9Directory(goCov, "gocov"), + qemu.WithAppendKernel("VMTEST_GOCOVERDIR=gocov"), + ) + return nil + } +} + // CollectKernelCoverage collects kernel coverage files for each test to // VMTEST_KERNEL_COVERAGE_DIR/{testName}/{instance}, where instance is a number // starting at 0. diff --git a/vendor/github.com/hugelgupf/vmtest/dependencies.go b/vendor/github.com/hugelgupf/vmtest/dependencies.go index f7a7168fd8..c35513de76 100644 --- a/vendor/github.com/hugelgupf/vmtest/dependencies.go +++ b/vendor/github.com/hugelgupf/vmtest/dependencies.go @@ -7,8 +7,15 @@ package vmtest // // But obviously aren't actually importable, since they are main packages. import ( + _ "github.com/u-root/u-root/cmds/core/cat" _ "github.com/u-root/u-root/cmds/core/dhclient" - _ "github.com/u-root/u-root/cmds/core/elvish" + _ "github.com/u-root/u-root/cmds/core/false" + _ "github.com/u-root/u-root/cmds/core/gosh" _ "github.com/u-root/u-root/cmds/core/init" _ "github.com/u-root/u-root/cmds/core/ip" + _ "github.com/u-root/u-root/cmds/core/ls" + _ "github.com/u-root/u-root/cmds/core/shutdown" + _ "github.com/u-root/u-root/cmds/core/sync" + _ "github.com/u-root/u-root/cmds/core/wget" + _ "github.com/u-root/u-root/cmds/exp/pxeserver" ) diff --git a/vendor/github.com/hugelgupf/vmtest/gotest.go b/vendor/github.com/hugelgupf/vmtest/gotest.go index 63dfeb29f2..d63bcc8732 100644 --- a/vendor/github.com/hugelgupf/vmtest/gotest.go +++ b/vendor/github.com/hugelgupf/vmtest/gotest.go @@ -19,6 +19,7 @@ import ( "github.com/hugelgupf/vmtest/testtmp" "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/uroot" + "github.com/u-root/uio/cp" "golang.org/x/tools/go/packages" ) @@ -158,21 +159,11 @@ func RunGoTestsInVM(t testing.TB, pkgs []string, opts ...GoTestOpt) { } // Set up u-root build options. - env := golang.Default(golang.DisableCGO(), golang.WithGOARCH(string(qemu.GuestArch().Arch()))) + env := golang.Default(golang.DisableCGO(), golang.WithGOARCH(string(qemu.GuestArch()))) // Statically build tests and add them to the temporary directory. testDir := filepath.Join(sharedDir, "tests") - if len(vmCoverProfile) > 0 { - f, err := os.Create(filepath.Join(sharedDir, "coverage.profile")) - if err != nil { - t.Fatalf("Could not create coverage file %v", err) - } - if err := f.Close(); err != nil { - t.Fatalf("Could not close coverage.profile: %v", err) - } - } - // Compile the Go tests. Place the test binaries in a directory that // will be shared with the VM using 9P. for _, pkg := range goOpts.Packages { @@ -207,49 +198,23 @@ func RunGoTestsInVM(t testing.TB, pkgs []string, opts ...GoTestOpt) { qemuFns := []qemu.Fn{ qemu.P9Directory(sharedDir, "gotests"), } - goCov := os.Getenv("GOCOVERDIR") - if goCov != "" { - qemuFns = append(qemuFns, - qemu.P9Directory(goCov, "gocov"), - qemu.WithAppendKernel("VMTEST_GOCOVERDIR=gocov"), - ) - } // Create the initramfs and start the VM. vm := StartVM(t, append( []Opt{ WithMergedInitramfs(initramfs), WithQEMUFn(qemuFns...), CollectKernelCoverage(), + ShareGOCOVERDIR(), }, goOpts.VMOpts...)...) - if _, err := vm.Console.ExpectString("TESTS PASSED MARKER"); err != nil { - t.Errorf("Waiting for 'TESTS PASSED MARKER' signal: %v", err) - } - if err := vm.Wait(); err != nil { t.Errorf("VM exited with %v", err) } // Collect Go coverage. if len(vmCoverProfile) > 0 { - cov, err := os.Open(filepath.Join(sharedDir, "coverage.profile")) - if err != nil { - t.Fatalf("No coverage file shared from VM: %v", err) - } - - out, err := os.OpenFile(vmCoverProfile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) - if err != nil { - t.Fatalf("Could not open vmcoverageprofile: %v", err) - } - - if _, err := io.Copy(out, cov); err != nil { - t.Fatalf("Error copying coverage: %s", err) - } - if err := out.Close(); err != nil { - t.Fatalf("Could not close vmcoverageprofile: %v", err) - } - if err := cov.Close(); err != nil { - t.Fatalf("Could not close coverage.profile: %v", err) + if err := cp.Copy(filepath.Join(sharedDir, "coverage.profile"), vmCoverProfile); err != nil { + t.Errorf("Could not copy coverage file: %v", err) } } diff --git a/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go b/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go index ba5aed0f57..6c1487e203 100644 --- a/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go +++ b/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go @@ -15,6 +15,29 @@ import ( "github.com/u-root/u-root/pkg/tarutil" ) +// GOCOVERDIR sets GOCOVERDIR in the guest if it was shared by +// vmtest.ShareGOCOVERDIR. +func GOCOVERDIR() func() { + tag := os.Getenv("VMTEST_GOCOVERDIR") + if tag == "" { + return func() {} + } + + mp, err := Mount9PDir("/gocov", tag) + if err != nil { + log.Fatal(err) + } + + if err := os.Setenv("GOCOVERDIR", "/gocov"); err != nil { + log.Fatal(err) + } + return func() { + if err := mp.Unmount(0); err != nil { + log.Printf("Unmounting GOCOVERDIR: %v", err) + } + } +} + // gcovFilter filters on all files ending with a gcda or gcno extension. func gcovFilter(hdr *tar.Header) bool { if hdr.Typeflag == tar.TypeDir { diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/devices.go b/vendor/github.com/hugelgupf/vmtest/qemu/devices.go index bbc7532c55..23a90868b1 100644 --- a/vendor/github.com/hugelgupf/vmtest/qemu/devices.go +++ b/vendor/github.com/hugelgupf/vmtest/qemu/devices.go @@ -14,12 +14,23 @@ import ( "net/http" "os" "strings" + "sync" "syscall" + "time" "github.com/creack/pty" "github.com/hugelgupf/vmtest/internal/eventchannel" ) +// ErrInvalidDir is used when no directory is specified for file sharing. +var ErrInvalidDir = errors.New("no directory specified") + +// ErrInvalidTag is used when no tag is specified for 9P file system sharing. +var ErrInvalidTag = errors.New("no tag specified for 9P file system") + +// ErrIsNotDir is used when the directory specified for file sharing is not a directory. +var ErrIsNotDir = errors.New("file system sharing requires directory") + // IDAllocator is used to ensure no overlapping QEMU option IDs. type IDAllocator struct { // maps a prefix to the maximum used suffix number. @@ -46,7 +57,16 @@ func (a *IDAllocator) ID(prefix string) string { func ReadOnlyDirectory(dir string) Fn { return func(alloc *IDAllocator, opts *Options) error { if len(dir) == 0 { - return nil + return ErrInvalidDir + } + if fi, err := os.Stat(dir); err != nil { + return fmt.Errorf("cannot access directory %s to be shared with guest: %w", dir, err) + } else if !fi.IsDir() { + return &os.PathError{ + Op: "9P-directory-sharing", + Path: dir, + Err: fmt.Errorf("%w: is %s", ErrIsNotDir, fi.Mode().Type()), + } } drive := alloc.ID("drive") @@ -65,8 +85,8 @@ func ReadOnlyDirectory(dir string) Fn { // IDEBlockDevice emulates an AHCI/IDE block device. func IDEBlockDevice(file string) Fn { return func(alloc *IDAllocator, opts *Options) error { - if len(file) == 0 { - return nil + if _, err := os.Stat(file); err != nil { + return fmt.Errorf("cannot access file %s to be shared with guest: %w", file, err) } drive := alloc.ID("drive") @@ -105,15 +125,19 @@ func P9BootDirectory(dir string) Fn { func p9Directory(dir string, boot bool, tag string) Fn { return func(alloc *IDAllocator, opts *Options) error { if len(dir) == 0 { - return fmt.Errorf("no directory specified for shared 9P file system") + return fmt.Errorf("%w for shared 9P file system", ErrInvalidDir) } if len(tag) == 0 { - return fmt.Errorf("a tag must be specified for 9P file system") + return ErrInvalidTag } if fi, err := os.Stat(dir); err != nil { - return fmt.Errorf("cannot access directory %s to be shared with guest: %v", dir, err) + return fmt.Errorf("cannot access directory %s to be shared with guest: %w", dir, err) } else if !fi.IsDir() { - return fmt.Errorf("directory %s to be shared with guest is not a directory, is %s", dir, fi.Mode().Type()) + return &os.PathError{ + Op: "9P-directory-sharing", + Path: dir, + Err: fmt.Errorf("%w: is %s", ErrIsNotDir, fi.Mode().Type()), + } } var id string @@ -146,9 +170,6 @@ func p9Directory(dir string, boot bool, tag string) Fn { "rootfstype=9p", "rootflags=trans=virtio,version=9p2000.L", ) - } else { - // seen as an env var by the init process - opts.AppendKernel("UROOT_USE_9P=1") } return nil } @@ -203,7 +224,10 @@ func ServeHTTP(s *http.Server, l net.Listener) Fn { }) opts.Tasks = append(opts.Tasks, func(ctx context.Context, n *Notifications) error { // Wait for VM exit. - <-n.VMExited + select { + case <-n.VMExited: + case <-ctx.Done(): + } // Stop HTTP server. return s.Close() }) @@ -211,9 +235,12 @@ func ServeHTTP(s *http.Server, l net.Listener) Fn { } } +// LinePrinter prints one line to some output. +type LinePrinter func(line string) + // LogSerialByLine processes serial output from the guest one line at a time // and calls callback on each full line. -func LogSerialByLine(callback func(line string)) Fn { +func LogSerialByLine(callback LinePrinter) Fn { return func(alloc *IDAllocator, opts *Options) error { r, w := io.Pipe() opts.SerialOutput = append(opts.SerialOutput, w) @@ -231,12 +258,41 @@ func LogSerialByLine(callback func(line string)) Fn { } } -// PrintLineWithPrefix returns a usable callback for LogSerialByLine that -// prints a prefix and the line. Usable with any standard Go print function -// like t.Logf or fmt.Printf. -func PrintLineWithPrefix(prefix string, printer func(fmt string, arg ...any)) func(line string) { +// TS prefixes line printer output with a timestamp since the first log line. +// +// format can be any Time.Format format string. Recommendations are +// time.TimeOnly or time.DateTime. +func TS(format string, printer LinePrinter) LinePrinter { + return func(line string) { + printer(fmt.Sprintf("[%s] %s", time.Now().Format(format), line)) + } +} + +// DefaultPrint is the default LinePrinter, adding a prefix and relative timestamp. +func DefaultPrint(prefix string, printer func(fmt string, arg ...any)) LinePrinter { + return RelativeTS(Prefix(prefix, PrintLine(printer))) +} + +// RelativeTS prefixes line printer output with "[%06.4fs] " seconds since the +// first log line. +func RelativeTS(printer LinePrinter) LinePrinter { + start := sync.OnceValue(time.Now) + return func(line string) { + printer(fmt.Sprintf("[%06.4fs] %s", time.Since(start()).Seconds(), line)) + } +} + +// PrintLine is a LinePrinter that prints to a standard "formatter" like testing.TB.Logf or fmt.Printf. +func PrintLine(printer func(fmt string, arg ...any)) LinePrinter { + return func(line string) { + printer("%s", line) + } +} + +// Prefix returns a LinePrinter that prefixes the given LinePrinter with "prefix: ". +func Prefix(prefix string, printer LinePrinter) LinePrinter { return func(line string) { - printer("%s: %s", prefix, line) + printer(fmt.Sprintf("%s: %s", prefix, line)) } } @@ -395,3 +451,49 @@ func Cleanup(f func() error) Task { return f() } } + +// ByArch applies only the Fn config function applicable to the VM guest +// architecture. +func ByArch(m map[Arch]Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + a := opts.Arch() + fn, ok := m[a] + if !ok { + return nil + } + return fn(alloc, opts) + } +} + +// IfNotArch applies fn only if the VM guest arch is not the given arch. +func IfNotArch(arch Arch, fn Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + if opts.Arch() == arch { + return nil + } + return fn(alloc, opts) + } +} + +// IfArch applies fn only if the VM guest arch is the given arch. +func IfArch(arch Arch, fn Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + if opts.Arch() == arch { + return fn(alloc, opts) + } + return nil + } +} + +// All applies all given configurators in order. If an error occurs, it returns +// the error early. +func All(fn ...Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + for _, f := range fn { + if err := f(alloc, opts); err != nil { + return err + } + } + return nil + } +} diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/network/network.go b/vendor/github.com/hugelgupf/vmtest/qemu/network/network.go deleted file mode 100644 index 190e089093..0000000000 --- a/vendor/github.com/hugelgupf/vmtest/qemu/network/network.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package network provides net device configurators for use with the Go qemu -// API. -package network - -import ( - "fmt" - "net" - "sync/atomic" - - "github.com/hugelgupf/vmtest/qemu" -) - -// InterVM is a Device that can connect multiple QEMU VMs to each other. -// -// InterVM uses the QEMU socket mechanism to connect multiple VMs with a simple -// TCP socket. -type InterVM struct { - port uint16 - - // numVMs must be atomically accessed so VMs can be started in parallel - // in goroutines. - numVMs uint32 -} - -// NewInterVM creates a new QEMU network between QEMU VMs. -// -// The network is closed from the world and only between the QEMU VMs. -func NewInterVM() *InterVM { - return &InterVM{ - port: 1234, - } -} - -// Opt returns additional QEMU command-line parameters based on the net -// device ID. -type Opt func(netdev string, id *qemu.IDAllocator) []string - -// WithPCAP captures network traffic and saves it to outputFile. -func WithPCAP(outputFile string) Opt { - return func(netdev string, id *qemu.IDAllocator) []string { - return []string{ - "-object", - fmt.Sprintf("filter-dump,id=%s,netdev=%s,file=%s", id.ID("filter"), netdev, outputFile), - } - } -} - -// NewVM returns a Device that can be used with a new QEMU VM. -func (n *InterVM) NewVM(nopts ...Opt) qemu.Fn { - if n == nil { - return nil - } - - newNum := atomic.AddUint32(&n.numVMs, 1) - num := newNum - 1 - - // MAC for the virtualized NIC. - // - // This is from the range of locally administered address ranges. - mac := net.HardwareAddr{0x0e, 0x00, 0x00, 0x00, 0x00, byte(num)} - return func(alloc *qemu.IDAllocator, opts *qemu.Options) error { - devID := alloc.ID("vm") - - args := []string{"-device", fmt.Sprintf("e1000,netdev=%s,mac=%s", devID, mac)} - // Note: QEMU in CircleCI seems to in solve cases fail when using just ':1234' format. - // It fails with "address resolution failed for :1234: Name or service not known" - // hinting that this is somehow related to DNS resolution. To work around this, - // we explicitly bind to 127.0.0.1 (IPv6 [::1] is not parsed correctly by QEMU). - if num != 0 { - args = append(args, "-netdev", fmt.Sprintf("socket,id=%s,connect=127.0.0.1:%d", devID, n.port)) - } else { - args = append(args, "-netdev", fmt.Sprintf("socket,id=%s,listen=127.0.0.1:%d", devID, n.port)) - } - - for _, opt := range nopts { - args = append(args, opt(devID, alloc)...) - } - opts.AppendQEMU(args...) - return nil - } -} - -// IPv4HostNetwork provides QEMU user-mode networking to the host. -// -// Net must be an IPv4 network. -// It uses the e1000 NIC. -func IPv4HostNetwork(net *net.IPNet, nopts ...Opt) qemu.Fn { - return func(alloc *qemu.IDAllocator, opts *qemu.Options) error { - if net.IP.To4() == nil { - return fmt.Errorf("HostNetwork must be configured with an IPv4 address") - } - - netdevID := alloc.ID("netdev") - args := []string{ - "-device", fmt.Sprintf("e1000,netdev=%s", netdevID), - "-netdev", fmt.Sprintf("user,id=%s,net=%s,dhcpstart=%s,ipv6=off", netdevID, net, nthIP(net, 8)), - } - - for _, opt := range nopts { - args = append(args, opt(netdevID, alloc)...) - } - opts.AppendQEMU(args...) - return nil - } -} - -func inc(ip net.IP) { - for j := len(ip) - 1; j >= 0; j-- { - ip[j]++ - if ip[j] > 0 { - break - } - } -} - -func nthIP(nt *net.IPNet, n int) net.IP { - ip := make(net.IP, net.IPv4len) - copy(ip, nt.IP.To4()) - for i := 0; i < n; i++ { - inc(ip) - } - if !nt.Contains(ip) { - return nil - } - return ip -} diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go b/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go index 0d7c0cdd3a..23b7280cf0 100644 --- a/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go +++ b/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go @@ -14,7 +14,9 @@ // Other environment variables: // // VMTEST_ARCH (used when Arch is empty or ArchUseEnvv is set) +// VMTEST_QEMU_APPEND (always added to QEMU arguments) // VMTEST_KERNEL (used when Options.Kernel is empty) +// VMTEST_KERNEL_APPEND (always added to kernel args) // VMTEST_INITRAMFS (used when Options.Initramfs is empty) // VMTEST_TIMEOUT (used when Options.VMTimeout is empty) package qemu @@ -40,12 +42,12 @@ import ( // ErrKernelRequiredForArgs is returned when KernelArgs is populated but Kernel is empty. var ErrKernelRequiredForArgs = errors.New("KernelArgs can only be used when Kernel is also specified due to how QEMU bootloader works") -// ErrNoArch is returned when neither Arch nor VMTEST_ARCH are set. -var ErrNoArch = errors.New("no guest architecture specified -- guest arch is required to decide some QEMU command-line arguments") - // ErrUnsupportedArch is returned when an unsupported guest architecture value is used. var ErrUnsupportedArch = errors.New("unsupported guest architecture specified -- guest arch is required to decide some QEMU command-line arguments") +// ErrInvalidTimeout is returned when VMTEST_TIMEOUT could not be parsed. +var ErrInvalidTimeout = errors.New("could not parse VMTEST_TIMEOUT") + // Arch is the QEMU guest architecture. type Arch string @@ -65,6 +67,9 @@ const ( // ArchArm is the arm 32bit architecture. ArchArm Arch = "arm" + + // ArchRiscv64 is the riscv 64bit architecture. + ArchRiscv64 Arch = "riscv64" ) // SupportedArches are the supported guest architecture values. @@ -73,6 +78,7 @@ var SupportedArches = []Arch{ ArchI386, ArchArm64, ArchArm, + ArchRiscv64, } // GuestArch returns the Guest architecture under test. Either VMTEST_ARCH or @@ -89,14 +95,6 @@ func (g Arch) Valid() bool { return slices.Contains(SupportedArches, g) } -// Arch returns the guest architecture. -func (g Arch) Arch() Arch { - if g == ArchUseEnvv { - g = GuestArch() - } - return g -} - // Fn is a QEMU configuration option supplied to Start or OptionsFor. // // Fns rely on a QEMU architecture already having been determined. @@ -179,20 +177,21 @@ func OptionsFor(arch Arch, fns ...Fn) (*Options, error) { var err error vmTimeout, err = time.ParseDuration(d) if err != nil { - return nil, fmt.Errorf("invalid VMTEST_TIMEOUT value: %w", err) + return nil, fmt.Errorf("%w: %v", ErrInvalidTimeout, err) } } o := &Options{ QEMUCommand: os.Getenv("VMTEST_QEMU"), Kernel: os.Getenv("VMTEST_KERNEL"), + KernelArgs: os.Getenv("VMTEST_KERNEL_APPEND"), Initramfs: os.Getenv("VMTEST_INITRAMFS"), VMTimeout: vmTimeout, // Disable graphics by default. - QEMUArgs: []string{"-nographic"}, + QEMUArgs: append([]string{"-nographic"}, strings.Fields(os.Getenv("VMTEST_QEMU_APPEND"))...), } - if err := o.setArch(arch.Arch()); err != nil { + if err := o.setArch(arch); err != nil { return nil, err } @@ -233,7 +232,7 @@ type Options struct { // If empty, VMTEST_QEMU_ARCH env var will be used. arch Arch - // QEMUCommand is QEMU binary to invoke and some additonal args. + // QEMUCommand is QEMU binary to invoke and some additional args. // // If empty, the VMTEST_QEMU env var will be used. QEMUCommand string @@ -249,6 +248,8 @@ type Options struct { Initramfs string // Extra kernel command-line arguments. + // + // VMTEST_KERNEL_APPEND env var will always be prepended. KernelArgs string // Where to send serial output. @@ -262,6 +263,8 @@ type Options struct { // QEMU subprocess exits. When the context is canceled, the QEMU // subprocess is expected to exit as well, and when the QEMU subprocess // exits, the context is canceled. + // + // Tasks may depend on ExtraFiles or SerialOutput to be closed to exit. Tasks []Task // Additional QEMU cmdline arguments. @@ -373,9 +376,22 @@ func (o *Options) Start(ctx context.Context) (*VM, error) { if err := cmd.Start(); err != nil { // Cancel tasks. cancel() - // Wait for tasks to exit. Some day we'll report their errors - // with errors.Join. + + // Unblock tasks that may depend on these files. + vm.Console.Close() + for _, w := range vm.Options.SerialOutput { + w.Close() + } + for _, c := range o.ExtraFiles { + c.Close() + } + + // Wait for tasks to exit. _ = vm.taskWG.Wait() + + // Close these after tasks have exited to guarantee that tasks + // use context cancelation or closing of their inputs to unblock. + vm.notifs.closeAll() return nil, err } vm.notifs.vmStarted() @@ -403,8 +419,8 @@ func (o *Options) Start(ctx context.Context) (*VM, error) { } func (o *Options) setArch(arch Arch) error { - if len(arch) == 0 { - return ErrNoArch + if arch == ArchUseEnvv { + arch = GuestArch() } if !arch.Valid() { return fmt.Errorf("%w: %s", ErrUnsupportedArch, arch) @@ -517,6 +533,14 @@ func (v *VM) Waited() bool { func (v *VM) Wait() error { v.waitCalled.Store(true) + // If there is a lot of output after the last user's Expect call (or + // there are no Expect calls at all), the pty buffer may fill up and + // the guest is blocked from writing anything and from continuing + // execution. + // + // Therefore, drain! EOF should happen when the guest exits. + _, _ = v.Console.ExpectEOF() + <-v.wait v.waitMu.Lock() @@ -565,3 +589,10 @@ func (n notifications) vmExited(err error) { close(m.VMExited) } } + +func (n notifications) closeAll() { + for _, m := range n { + close(m.VMStarted) + close(m.VMExited) + } +} diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/qnetwork/network.go b/vendor/github.com/hugelgupf/vmtest/qemu/qnetwork/network.go new file mode 100644 index 0000000000..5dc71b7885 --- /dev/null +++ b/vendor/github.com/hugelgupf/vmtest/qemu/qnetwork/network.go @@ -0,0 +1,263 @@ +// Copyright 2018 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package qnetwork provides net device configurators for use with the Go qemu +// API. +package qnetwork + +import ( + "context" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + + "github.com/hugelgupf/vmtest/qemu" +) + +// NIC is a QEMU NIC device string. +// +// Valid values for your QEMU can be found with `qemu-system- -device +// help` in the Network devices section. +type NIC string + +// A subset of QEMU NIC devices. +const ( + NICE1000 NIC = "e1000" + NICVirtioNet NIC = "virtio-net" +) + +// DeviceOptions are network device options. +// +// These are options for the `-device ,...` command-line arg. +type DeviceOptions struct { + // NIC is the NIC device that QEMU emulates. + NIC NIC + + // MAC is the MAC address assigned to this interface in the guest. + MAC net.HardwareAddr +} + +// SetNIC sets the NIC. +func (d *DeviceOptions) setNIC(nic NIC) { + d.NIC = nic +} + +// SetMAC sets the device's MAC. +func (d *DeviceOptions) setMAC(mac net.HardwareAddr) { + d.MAC = mac +} + +// DeviceOptioner is an interface for setting DeviceOptions members. +// +// It exists so DeviceOptions can be extensible through generics, but the +// WithNIC/WithMAC/WithPCAP functions can be the same across all Options +// structs. +type DeviceOptioner interface { + *UserOptions | *DeviceOptions + + setNIC(NIC) + setMAC(net.HardwareAddr) +} + +// Opt is a configurer useed with either *DeviceOptions or *UserOptions. +type Opt[DO DeviceOptioner] func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts DO) error + +// WithPCAP captures network traffic and saves it to outputFile. +func WithPCAP[DO DeviceOptioner](outputFile string) Opt[DO] { + return func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts DO) error { + qopts.AppendQEMU( + "-object", + fmt.Sprintf("filter-dump,id=%s,netdev=%s,file=%s", id.ID("filter"), netdev, outputFile), + ) + return nil + } +} + +// WithNIC changes the default NIC device QEMU emulates from e1000 to the given value. +func WithNIC[DO DeviceOptioner](nic NIC) Opt[DO] { + return func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts DO) error { + opts.setNIC(nic) + return nil + } +} + +// WithMAC assigns a MAC address to the guest interface. +func WithMAC[DO DeviceOptioner](mac net.HardwareAddr) Opt[DO] { + return func(netdev string, id *qemu.IDAllocator, qops *qemu.Options, opts DO) error { + if mac != nil { + opts.setMAC(mac) + } + return nil + } +} + +// InterVM is a Device that can connect multiple QEMU VMs to each other. +// +// InterVM uses the QEMU socket mechanism to connect multiple VMs with a simple +// unix domain socket. +type InterVM struct { + socket string + err error + + // numVMs must be atomically accessed so VMs can be started in parallel + // in goroutines. + numVMs uint32 + + wg sync.WaitGroup +} + +// NewInterVM creates a new QEMU network between QEMU VMs. +// +// The network is closed from the world and only between the QEMU VMs. +func NewInterVM() *InterVM { + // Avoid returning an error here if unnecessary. + dir, err := os.MkdirTemp("", "intervm-") + return &InterVM{ + err: err, + socket: filepath.Join(dir, "intervm.socket"), + } +} + +// NewVM returns a Device that can be used with a new QEMU VM. +func (n *InterVM) NewVM(nopts ...Opt[*DeviceOptions]) qemu.Fn { + if n == nil { + return nil + } + + newNum := atomic.AddUint32(&n.numVMs, 1) + num := newNum - 1 + + n.wg.Add(1) + return func(alloc *qemu.IDAllocator, qopts *qemu.Options) error { + if n.err != nil { + return n.err + } + devID := alloc.ID("vm") + + opts := DeviceOptions{ + // Default NIC. + NIC: NICE1000, + + // MAC for the virtualized NIC. + // + // This is from the range of locally administered address ranges. + MAC: net.HardwareAddr{0xe, 0, 0, 0, 0, byte(num)}, + } + for _, opt := range nopts { + if err := opt(devID, alloc, qopts, &opts); err != nil { + return err + } + } + args := []string{"-device", fmt.Sprintf("%s,netdev=%s,mac=%s", opts.NIC, devID, opts.MAC)} + + if num != 0 { + args = append(args, "-netdev", fmt.Sprintf("stream,id=%s,server=false,addr.type=unix,addr.path=%s", devID, n.socket)) + } else { + args = append(args, "-netdev", fmt.Sprintf("stream,id=%s,server=true,addr.type=unix,addr.path=%s", devID, n.socket)) + + // When the server VM exits, wait until all clients + // close, then delete the socket file and directory. + qopts.Tasks = append(qopts.Tasks, func(ctx context.Context, notif *qemu.Notifications) error { + n.wg.Wait() + return os.RemoveAll(filepath.Dir(n.socket)) + }) + } + + // When each VM exits, call Done. + qopts.Tasks = append(qopts.Tasks, qemu.Cleanup(func() error { + n.wg.Done() + return nil + })) + qopts.AppendQEMU(args...) + return nil + } +} + +// UserOptions are options for a QEMU "user" network. +type UserOptions struct { + DeviceOptions + + Args []string +} + +// WithUserArg adds more comma-separated args to a `-netdev user,arg0,arg1,...` +// invocation. +func WithUserArg(arg ...string) Opt[*UserOptions] { + return func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts *UserOptions) error { + opts.Args = append(opts.Args, arg...) + return nil + } +} + +// IPv4HostNetwork provides QEMU user-mode networking to the host. +// +// Net must be an IPv4 network. +// +// Default NIC is e1000, with a MAC address of 0e:00:00:00:00:01. +func IPv4HostNetwork(cidr string, nopts ...Opt[*UserOptions]) qemu.Fn { + return func(alloc *qemu.IDAllocator, qopts *qemu.Options) error { + // TODO: use IP + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + if ipnet.IP.To4() == nil { + return fmt.Errorf("HostNetwork must be configured with an IPv4 address") + } + + netdevID := alloc.ID("netdev") + opts := UserOptions{ + DeviceOptions: DeviceOptions{ + // Default NIC. + NIC: NICE1000, + + // MAC for the virtualized NIC. + // + // This is from the range of locally administered address ranges. + MAC: net.HardwareAddr{0xe, 0, 0, 0, 0, 1}, + }, + } + + for _, opt := range nopts { + if err := opt(netdevID, alloc, qopts, &opts); err != nil { + return err + } + } + + var extraUserArgs string + if len(opts.Args) > 0 { + extraUserArgs = "," + strings.Join(opts.Args, ",") + } + qopts.AppendQEMU( + "-device", fmt.Sprintf("%s,netdev=%s,mac=%s", opts.NIC, netdevID, opts.MAC), + "-netdev", fmt.Sprintf("user,id=%s,net=%s,dhcpstart=%s,ipv6=off%s", netdevID, ipnet, nthIP(ipnet, 8), extraUserArgs), + ) + return nil + } +} + +func inc(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +func nthIP(nt *net.IPNet, n int) net.IP { + ip := make(net.IP, net.IPv4len) + copy(ip, nt.IP.To4()) + for i := 0; i < n; i++ { + inc(ip) + } + if !nt.Contains(ip) { + return nil + } + return ip +} diff --git a/vendor/github.com/hugelgupf/vmtest/shelltest.go b/vendor/github.com/hugelgupf/vmtest/shelltest.go index 34b890024a..2bbbc1f468 100644 --- a/vendor/github.com/hugelgupf/vmtest/shelltest.go +++ b/vendor/github.com/hugelgupf/vmtest/shelltest.go @@ -15,22 +15,19 @@ import ( "github.com/u-root/u-root/pkg/uroot" ) -// RunCmdsInVM starts a VM and runs each command provided in testCmds in a -// shell in the VM. If any command fails, the test fails. +// RunCmdsInVM starts a VM and runs the given script using gosh in the guest. +// If any command fails, the test fails. // // The VM can be configured with o. The kernel can be provided via o or // VMTEST_KERNEL env var. Guest architecture can be set with VMTEST_ARCH. // -// Underneath, this generates an Elvish script with these commands. The script -// is shared with the VM and run from a special init. -// // - TODO: timeouts for individual individual commands. // - TODO: It should check their exit status. Hahaha. -func RunCmdsInVM(t *testing.T, testCmds []string, o ...Opt) { - vm := StartVMAndRunCmds(t, testCmds, o...) +func RunCmdsInVM(t testing.TB, script string, o ...Opt) { + vm := StartVMAndRunCmds(t, script, o...) if _, err := vm.Console.ExpectString("TESTS PASSED MARKER"); err != nil { - t.Errorf("Waiting for 'TESTS PASSED MARKER' signal: %v", err) + t.Errorf("Waiting for 'TESTS PASSED MARKER' failed -- script likely failed: %v", err) } if err := vm.Wait(); err != nil { @@ -38,22 +35,19 @@ func RunCmdsInVM(t *testing.T, testCmds []string, o ...Opt) { } } -// StartVMAndRunCmds starts a VM and runs each command provided in testCmds in -// a shell in the VM. If the commands return, the VM will be shutdown. +// StartVMAndRunCmds starts a VM and runs the script using gosh in the guest. +// If the commands return, the VM will be shutdown. // // The VM can be configured with o. -// -// Underneath, this generates an Elvish script with these commands. The script -// is shared with the VM and run from a special init. -func StartVMAndRunCmds(t *testing.T, testCmds []string, o ...Opt) *qemu.VM { +func StartVMAndRunCmds(t testing.TB, script string, o ...Opt) *qemu.VM { SkipWithoutQEMU(t) sharedDir := testtmp.TempDir(t) - // Generate Elvish shell script of test commands in o.SharedDir. - if len(testCmds) > 0 { - testFile := filepath.Join(sharedDir, "test.elv") - if err := os.WriteFile(testFile, []byte(strings.Join(testCmds, "\n")), 0o777); err != nil { + // Generate gosh shell script of test commands in o.SharedDir. + if len(script) > 0 { + testFile := filepath.Join(sharedDir, "test.sh") + if err := os.WriteFile(testFile, []byte(strings.Join([]string{"set -ex", script}, "\n")), 0o777); err != nil { t.Fatal(err) } } @@ -61,7 +55,7 @@ func StartVMAndRunCmds(t *testing.T, testCmds []string, o ...Opt) *qemu.VM { initramfs := uroot.Opts{ Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/init", - "github.com/u-root/u-root/cmds/core/elvish", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/hugelgupf/vmtest/vminit/shelluinit", ), InitCmd: "init", @@ -72,5 +66,6 @@ func StartVMAndRunCmds(t *testing.T, testCmds []string, o ...Opt) *qemu.VM { WithQEMUFn(qemu.P9Directory(sharedDir, "shelltest")), WithMergedInitramfs(initramfs), CollectKernelCoverage(), + ShareGOCOVERDIR(), }, o...)...) } diff --git a/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/gouinit.go b/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go similarity index 92% rename from vendor/github.com/hugelgupf/vmtest/vminit/gouinit/gouinit.go rename to vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go index c6c5c31d39..b86164235f 100644 --- a/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/gouinit.go +++ b/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go @@ -75,41 +75,45 @@ func AppendFile(srcFile, targetFile string) error { func runTest() error { flag.Parse() - cleanup, err := guest.MountSharedDir() + // If these fail, the host will be missing the "Done" event from + // testEvents, or possibly even the errors.json file and fail. + mp, err := guest.Mount9PDir("/gotestdata", "gotests") if err != nil { return err } - defer cleanup() - defer guest.CollectKernelCoverage() + defer func() { _ = mp.Unmount(0) }() - mp, err := guest.Mount9PDir("/gotestdata", "gotests") + testEvents, err := guest.EventChannel[testevent.ErrorEvent]("/gotestdata/errors.json") if err != nil { return err } - defer func() { _ = mp.Unmount(0) }() - - var envv []string - if tag := os.Getenv("VMTEST_GOCOVERDIR"); tag != "" { - mp, err := guest.Mount9PDir("/gocov", tag) - if err != nil { - return err - } - defer func() { _ = mp.Unmount(0) }() + defer testEvents.Close() - envv = append(envv, "GOCOVERDIR=/gocov") + if err := run(testEvents); err != nil { + _ = testEvents.Emit(testevent.ErrorEvent{ + Error: fmt.Sprintf("running tests failed: %v", err), + }) + return err } + return nil +} - goTestEvents, err := guest.EventChannel[json2test.TestEvent]("/gotestdata/results.json") +func run(testEvents *guest.Emitter[testevent.ErrorEvent]) error { + cleanup, err := guest.MountSharedDir() if err != nil { return err } - defer goTestEvents.Close() + defer cleanup() + defer guest.CollectKernelCoverage() - testEvents, err := guest.EventChannel[testevent.ErrorEvent]("/gotestdata/errors.json") + covCleanup := guest.GOCOVERDIR() + defer covCleanup() + + goTestEvents, err := guest.EventChannel[json2test.TestEvent]("/gotestdata/results.json") if err != nil { return err } - defer testEvents.Close() + defer goTestEvents.Close() return walkTests("/gotestdata/tests", func(path, pkgName string) { // Send the kill signal with a 500ms grace period. @@ -130,7 +134,6 @@ func runTest() error { cmd := exec.CommandContext(ctx, path, args...) cmd.Stdin, cmd.Stderr = os.Stdin, os.Stderr - cmd.Env = append(os.Environ(), envv...) // Write to stdout for humans, write to w for the JSON converter. // @@ -198,11 +201,10 @@ func runTest() error { } func main() { + flag.Parse() + if err := runTest(); err != nil { log.Printf("Tests failed: %v", err) - } else { - // The test infra is expecting this exact print. - log.Print("TESTS PASSED MARKER") } if err := unix.Reboot(unix.LINUX_REBOOT_CMD_POWER_OFF); err != nil { diff --git a/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/shelluinit.go b/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go similarity index 83% rename from vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/shelluinit.go rename to vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go index d6be651c3b..9709080aa0 100644 --- a/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/shelluinit.go +++ b/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go @@ -24,22 +24,25 @@ func runTest() error { defer cleanup() defer guest.CollectKernelCoverage() + covCleanup := guest.GOCOVERDIR() + defer covCleanup() + mp, err := guest.Mount9PDir("/shelltestdata", "shelltest") if err != nil { return err } defer func() { _ = mp.Unmount(0) }() - // Run the test script test.elv - test := "/shelltestdata/test.elv" + // Run the test script test.sh + test := "/shelltestdata/test.sh" if _, err := os.Stat(test); os.IsNotExist(err) { return errors.New("could not find any test script to run") } - cmd := exec.Command("elvish", test) + cmd := exec.Command("gosh", test) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr if err := cmd.Run(); err != nil { - return fmt.Errorf("test.elv ran unsuccessfully: %v", err) + return fmt.Errorf("test.sh ran unsuccessfully: %v", err) } return nil } diff --git a/vendor/github.com/hugelgupf/vmtest/vmtest.go b/vendor/github.com/hugelgupf/vmtest/vmtest.go index 57b7455744..45a486919c 100644 --- a/vendor/github.com/hugelgupf/vmtest/vmtest.go +++ b/vendor/github.com/hugelgupf/vmtest/vmtest.go @@ -17,6 +17,7 @@ import ( "github.com/hugelgupf/vmtest/qemu" "github.com/hugelgupf/vmtest/testtmp" "github.com/hugelgupf/vmtest/uqemu" + "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/ulog/ulogtest" "github.com/u-root/u-root/pkg/uroot" "golang.org/x/exp/maps" @@ -118,6 +119,9 @@ func (v *VMOptions) MergeInitramfs(buildOpts uroot.Opts) error { if buildOpts.DefaultShell != "" { v.Initramfs.DefaultShell = buildOpts.DefaultShell } + if buildOpts.BuildOpts != nil { + v.Initramfs.BuildOpts = buildOpts.BuildOpts + } return nil } @@ -160,6 +164,9 @@ func WithMergedInitramfs(o uroot.Opts) Opt { } // WithBusyboxCommands merges more busybox commands into the initramfs build options. +// +// Note that busybox rewrites commands, so if attempting to get integration +// test coverage of commands, use WithBinaryCommands. func WithBusyboxCommands(cmds ...string) Opt { return func(_ testing.TB, v *VMOptions) error { return v.MergeInitramfs(uroot.Opts{ @@ -168,6 +175,15 @@ func WithBusyboxCommands(cmds ...string) Opt { } } +// WithBinaryCommands merges more binary commands into the initramfs build options. +func WithBinaryCommands(cmds ...string) Opt { + return func(_ testing.TB, v *VMOptions) error { + return v.MergeInitramfs(uroot.Opts{ + Commands: uroot.BinaryCmds(cmds...), + }) + } +} + // WithInitramfsFiles merges more extra files into the initramfs build options. // Syntax is like u-root's ExtraFiles. func WithInitramfsFiles(files ...string) Opt { @@ -178,7 +194,21 @@ func WithInitramfsFiles(files ...string) Opt { } } -// WithSharedDir shares a directory with the VM. +// WithGoBuildOpts replaces Go build options for the initramfs. +func WithGoBuildOpts(g *golang.BuildOpts) Opt { + return func(_ testing.TB, v *VMOptions) error { + return v.MergeInitramfs(uroot.Opts{ + BuildOpts: g, + }) + } +} + +// WithSharedDir shares a directory with the QEMU VM using 9P using the +// tag "tmpdir". +// +// guest.MountSharedDir mounts this directory at /testdata. +// +// If none is set, no directory is shared with the guest by default. func WithSharedDir(dir string) Opt { return func(_ testing.TB, v *VMOptions) error { v.SharedDir = dir @@ -221,12 +251,10 @@ func startVM(t testing.TB, o *VMOptions) *qemu.VM { SkipWithoutQEMU(t) qopts := []qemu.Fn{ - qemu.LogSerialByLine(qemu.PrintLineWithPrefix(o.ConsoleOutputPrefix, t.Logf)), + qemu.LogSerialByLine(qemu.DefaultPrint(o.ConsoleOutputPrefix, t.Logf)), // Tests use this env var to identify they are running inside a // vmtest using SkipIfNotInVM. - // - // Older tests use the presence of uroot.vmtest in the kernel command-line. - qemu.WithAppendKernel("VMTEST_IN_GUEST=1", "uroot.vmtest"), + qemu.WithAppendKernel("VMTEST_IN_GUEST=1"), qemu.VirtioRandom(), } if o.SharedDir != "" { diff --git a/vendor/github.com/u-root/uio/uio/lazy.go b/vendor/github.com/u-root/uio/uio/lazy.go index a5cabca557..4cb06ac32b 100644 --- a/vendor/github.com/u-root/uio/uio/lazy.go +++ b/vendor/github.com/u-root/uio/uio/lazy.go @@ -10,19 +10,36 @@ import ( "os" ) +// ReadOneByte reads one byte from given io.ReaderAt. +func ReadOneByte(r io.ReaderAt) error { + buf := make([]byte, 1) + n, err := r.ReadAt(buf, 0) + if err != nil { + return err + } + if n != 1 { + return fmt.Errorf("expected to read 1 byte, but got %d", n) + } + return nil +} + // LazyOpener is a lazy io.Reader. // // LazyOpener will use a given open function to derive an io.Reader when Read // is first called on the LazyOpener. type LazyOpener struct { r io.Reader + s string err error open func() (io.Reader, error) } // NewLazyOpener returns a lazy io.Reader based on `open`. -func NewLazyOpener(open func() (io.Reader, error)) io.ReadCloser { - return &LazyOpener{open: open} +func NewLazyOpener(filename string, open func() (io.Reader, error)) *LazyOpener { + if len(filename) == 0 { + return nil + } + return &LazyOpener{s: filename, open: open} } // Read implements io.Reader.Read lazily. @@ -39,6 +56,17 @@ func (lr *LazyOpener) Read(p []byte) (int, error) { return lr.r.Read(p) } +// String implements fmt.Stringer. +func (lr *LazyOpener) String() string { + if len(lr.s) > 0 { + return lr.s + } + if lr.r != nil { + return fmt.Sprintf("%v", lr.r) + } + return "unopened mystery file" +} + // Close implements io.Closer.Close. func (lr *LazyOpener) Close() error { if c, ok := lr.r.(io.Closer); ok { @@ -52,10 +80,11 @@ func (lr *LazyOpener) Close() error { // LazyOpenerAt will use a given open function to derive an io.ReaderAt when // ReadAt is first called. type LazyOpenerAt struct { - r io.ReaderAt - s string - err error - open func() (io.ReaderAt, error) + r io.ReaderAt + s string + err error + limit int64 + open func() (io.ReaderAt, error) } // NewLazyFile returns a lazy ReaderAt opened from path. @@ -68,9 +97,24 @@ func NewLazyFile(path string) *LazyOpenerAt { }) } +// NewLazyLimitFile returns a lazy ReaderAt opened from path with a limit reader on it. +func NewLazyLimitFile(path string, limit int64) *LazyOpenerAt { + if len(path) == 0 { + return nil + } + return NewLazyLimitOpenerAt(path, limit, func() (io.ReaderAt, error) { + return os.Open(path) + }) +} + // NewLazyOpenerAt returns a lazy io.ReaderAt based on `open`. func NewLazyOpenerAt(filename string, open func() (io.ReaderAt, error)) *LazyOpenerAt { - return &LazyOpenerAt{s: filename, open: open} + return &LazyOpenerAt{s: filename, open: open, limit: -1} +} + +// NewLazyLimitOpenerAt returns a lazy io.ReaderAt based on `open`. +func NewLazyLimitOpenerAt(filename string, limit int64, open func() (io.ReaderAt, error)) *LazyOpenerAt { + return &LazyOpenerAt{s: filename, open: open, limit: limit} } // String implements fmt.Stringer. @@ -84,6 +128,15 @@ func (loa *LazyOpenerAt) String() string { return "unopened mystery file" } +// File returns the backend file of the io.ReaderAt if it +// is backed by a os.File. +func (loa *LazyOpenerAt) File() *os.File { + if f, ok := loa.r.(*os.File); ok { + return f + } + return nil +} + // ReadAt implements io.ReaderAt.ReadAt. func (loa *LazyOpenerAt) ReadAt(p []byte, off int64) (int, error) { if loa.r == nil && loa.err == nil { @@ -92,6 +145,14 @@ func (loa *LazyOpenerAt) ReadAt(p []byte, off int64) (int, error) { if loa.err != nil { return 0, loa.err } + if loa.limit > 0 { + if off >= loa.limit { + return 0, io.EOF + } + if int64(len(p)) > loa.limit-off { + p = p[0 : loa.limit-off] + } + } return loa.r.ReadAt(p, off) } diff --git a/pkg/uio/uiotest/uiotest.go b/vendor/github.com/u-root/uio/uio/uiotest/uiotest.go similarity index 71% rename from pkg/uio/uiotest/uiotest.go rename to vendor/github.com/u-root/uio/uio/uiotest/uiotest.go index 826bc51951..5e670beb91 100644 --- a/pkg/uio/uiotest/uiotest.go +++ b/vendor/github.com/u-root/uio/uio/uiotest/uiotest.go @@ -2,18 +2,26 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Package uiotest contains tests for uio functions. package uiotest import ( "io" "testing" + "time" - "github.com/u-root/u-root/pkg/testutil" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) +// NowLog returns the current time formatted like the standard log package's +// timestamp. +func NowLog() string { + return time.Now().Format("2006/01/02 15:04:05") +} + // TestLineWriter is an io.Writer that logs full lines of serial to tb. func TestLineWriter(tb testing.TB, prefix string) io.WriteCloser { + tb.Helper() if len(prefix) > 0 { return uio.FullLineWriter(&testLinePrefixWriter{tb: tb, prefix: prefix}) } @@ -27,7 +35,7 @@ type testLinePrefixWriter struct { } func (tsw *testLinePrefixWriter) OneLine(p []byte) { - tsw.tb.Logf("%s %s: %s", testutil.NowLog(), tsw.prefix, p) + tsw.tb.Logf("%s %s: %s", NowLog(), tsw.prefix, p) } // testLineWriter is an io.Writer that logs full lines of serial to tb. @@ -36,5 +44,5 @@ type testLineWriter struct { } func (tsw *testLineWriter) OneLine(p []byte) { - tsw.tb.Logf("%s: %s", testutil.NowLog(), p) + tsw.tb.Logf("%s: %s", NowLog(), p) } diff --git a/vendor/golang.org/x/net/icmp/dstunreach.go b/vendor/golang.org/x/net/icmp/dstunreach.go new file mode 100644 index 0000000000..8615cf54a4 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/dstunreach.go @@ -0,0 +1,59 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// A DstUnreach represents an ICMP destination unreachable message +// body. +type DstUnreach struct { + Data []byte // data, known as original datagram field + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *DstUnreach) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *DstUnreach) Marshal(proto int) ([]byte, error) { + var typ Type + switch proto { + case iana.ProtocolICMP: + typ = ipv4.ICMPTypeDestinationUnreachable + case iana.ProtocolIPv6ICMP: + typ = ipv6.ICMPTypeDestinationUnreachable + default: + return nil, errInvalidProtocol + } + if !validExtensions(typ, p.Extensions) { + return nil, errInvalidExtension + } + return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions) +} + +// parseDstUnreach parses b as an ICMP destination unreachable message +// body. +func parseDstUnreach(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &DstUnreach{} + var err error + p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/echo.go b/vendor/golang.org/x/net/icmp/echo.go new file mode 100644 index 0000000000..b591864278 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/echo.go @@ -0,0 +1,173 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// An Echo represents an ICMP echo request or reply message body. +type Echo struct { + ID int // identifier + Seq int // sequence number + Data []byte // data +} + +// Len implements the Len method of MessageBody interface. +func (p *Echo) Len(proto int) int { + if p == nil { + return 0 + } + return 4 + len(p.Data) +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *Echo) Marshal(proto int) ([]byte, error) { + b := make([]byte, 4+len(p.Data)) + binary.BigEndian.PutUint16(b[:2], uint16(p.ID)) + binary.BigEndian.PutUint16(b[2:4], uint16(p.Seq)) + copy(b[4:], p.Data) + return b, nil +} + +// parseEcho parses b as an ICMP echo request or reply message body. +func parseEcho(proto int, _ Type, b []byte) (MessageBody, error) { + bodyLen := len(b) + if bodyLen < 4 { + return nil, errMessageTooShort + } + p := &Echo{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(binary.BigEndian.Uint16(b[2:4]))} + if bodyLen > 4 { + p.Data = make([]byte, bodyLen-4) + copy(p.Data, b[4:]) + } + return p, nil +} + +// An ExtendedEchoRequest represents an ICMP extended echo request +// message body. +type ExtendedEchoRequest struct { + ID int // identifier + Seq int // sequence number + Local bool // must be true when identifying by name or index + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *ExtendedEchoRequest) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, false, nil, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *ExtendedEchoRequest) Marshal(proto int) ([]byte, error) { + var typ Type + switch proto { + case iana.ProtocolICMP: + typ = ipv4.ICMPTypeExtendedEchoRequest + case iana.ProtocolIPv6ICMP: + typ = ipv6.ICMPTypeExtendedEchoRequest + default: + return nil, errInvalidProtocol + } + if !validExtensions(typ, p.Extensions) { + return nil, errInvalidExtension + } + b, err := marshalMultipartMessageBody(proto, false, nil, p.Extensions) + if err != nil { + return nil, err + } + binary.BigEndian.PutUint16(b[:2], uint16(p.ID)) + b[2] = byte(p.Seq) + if p.Local { + b[3] |= 0x01 + } + return b, nil +} + +// parseExtendedEchoRequest parses b as an ICMP extended echo request +// message body. +func parseExtendedEchoRequest(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &ExtendedEchoRequest{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(b[2])} + if b[3]&0x01 != 0 { + p.Local = true + } + var err error + _, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} + +// An ExtendedEchoReply represents an ICMP extended echo reply message +// body. +type ExtendedEchoReply struct { + ID int // identifier + Seq int // sequence number + State int // 3-bit state working together with Message.Code + Active bool // probed interface is active + IPv4 bool // probed interface runs IPv4 + IPv6 bool // probed interface runs IPv6 +} + +// Len implements the Len method of MessageBody interface. +func (p *ExtendedEchoReply) Len(proto int) int { + if p == nil { + return 0 + } + return 4 +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *ExtendedEchoReply) Marshal(proto int) ([]byte, error) { + b := make([]byte, 4) + binary.BigEndian.PutUint16(b[:2], uint16(p.ID)) + b[2] = byte(p.Seq) + b[3] = byte(p.State<<5) & 0xe0 + if p.Active { + b[3] |= 0x04 + } + if p.IPv4 { + b[3] |= 0x02 + } + if p.IPv6 { + b[3] |= 0x01 + } + return b, nil +} + +// parseExtendedEchoReply parses b as an ICMP extended echo reply +// message body. +func parseExtendedEchoReply(proto int, _ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &ExtendedEchoReply{ + ID: int(binary.BigEndian.Uint16(b[:2])), + Seq: int(b[2]), + State: int(b[3]) >> 5, + } + if b[3]&0x04 != 0 { + p.Active = true + } + if b[3]&0x02 != 0 { + p.IPv4 = true + } + if b[3]&0x01 != 0 { + p.IPv6 = true + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/endpoint.go b/vendor/golang.org/x/net/icmp/endpoint.go new file mode 100644 index 0000000000..47f5b698d5 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/endpoint.go @@ -0,0 +1,113 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "net" + "runtime" + "time" + + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +var _ net.PacketConn = &PacketConn{} + +// A PacketConn represents a packet network endpoint that uses either +// ICMPv4 or ICMPv6. +type PacketConn struct { + c net.PacketConn + p4 *ipv4.PacketConn + p6 *ipv6.PacketConn +} + +func (c *PacketConn) ok() bool { return c != nil && c.c != nil } + +// IPv4PacketConn returns the ipv4.PacketConn of c. +// It returns nil when c is not created as the endpoint for ICMPv4. +func (c *PacketConn) IPv4PacketConn() *ipv4.PacketConn { + if !c.ok() { + return nil + } + return c.p4 +} + +// IPv6PacketConn returns the ipv6.PacketConn of c. +// It returns nil when c is not created as the endpoint for ICMPv6. +func (c *PacketConn) IPv6PacketConn() *ipv6.PacketConn { + if !c.ok() { + return nil + } + return c.p6 +} + +// ReadFrom reads an ICMP message from the connection. +func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + if !c.ok() { + return 0, nil, errInvalidConn + } + // Please be informed that ipv4.NewPacketConn enables + // IP_STRIPHDR option by default on Darwin. + // See golang.org/issue/9395 for further information. + if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && c.p4 != nil { + n, _, peer, err := c.p4.ReadFrom(b) + return n, peer, err + } + return c.c.ReadFrom(b) +} + +// WriteTo writes the ICMP message b to dst. +// The provided dst must be net.UDPAddr when c is a non-privileged +// datagram-oriented ICMP endpoint. +// Otherwise it must be net.IPAddr. +func (c *PacketConn) WriteTo(b []byte, dst net.Addr) (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + return c.c.WriteTo(b, dst) +} + +// Close closes the endpoint. +func (c *PacketConn) Close() error { + if !c.ok() { + return errInvalidConn + } + return c.c.Close() +} + +// LocalAddr returns the local network address. +func (c *PacketConn) LocalAddr() net.Addr { + if !c.ok() { + return nil + } + return c.c.LocalAddr() +} + +// SetDeadline sets the read and write deadlines associated with the +// endpoint. +func (c *PacketConn) SetDeadline(t time.Time) error { + if !c.ok() { + return errInvalidConn + } + return c.c.SetDeadline(t) +} + +// SetReadDeadline sets the read deadline associated with the +// endpoint. +func (c *PacketConn) SetReadDeadline(t time.Time) error { + if !c.ok() { + return errInvalidConn + } + return c.c.SetReadDeadline(t) +} + +// SetWriteDeadline sets the write deadline associated with the +// endpoint. +func (c *PacketConn) SetWriteDeadline(t time.Time) error { + if !c.ok() { + return errInvalidConn + } + return c.c.SetWriteDeadline(t) +} diff --git a/vendor/golang.org/x/net/icmp/extension.go b/vendor/golang.org/x/net/icmp/extension.go new file mode 100644 index 0000000000..eeb85c3fc0 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/extension.go @@ -0,0 +1,170 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// An Extension represents an ICMP extension. +type Extension interface { + // Len returns the length of ICMP extension. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Len(proto int) int + + // Marshal returns the binary encoding of ICMP extension. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Marshal(proto int) ([]byte, error) +} + +const extensionVersion = 2 + +func validExtensionHeader(b []byte) bool { + v := int(b[0]&0xf0) >> 4 + s := binary.BigEndian.Uint16(b[2:4]) + if s != 0 { + s = checksum(b) + } + if v != extensionVersion || s != 0 { + return false + } + return true +} + +// parseExtensions parses b as a list of ICMP extensions. +// The length attribute l must be the length attribute field in +// received icmp messages. +// +// It will return a list of ICMP extensions and an adjusted length +// attribute that represents the length of the padded original +// datagram field. Otherwise, it returns an error. +func parseExtensions(typ Type, b []byte, l int) ([]Extension, int, error) { + // Still a lot of non-RFC 4884 compliant implementations are + // out there. Set the length attribute l to 128 when it looks + // inappropriate for backwards compatibility. + // + // A minimal extension at least requires 8 octets; 4 octets + // for an extension header, and 4 octets for a single object + // header. + // + // See RFC 4884 for further information. + switch typ { + case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest: + if len(b) < 8 || !validExtensionHeader(b) { + return nil, -1, errNoExtension + } + l = 0 + default: + if 128 > l || l+8 > len(b) { + l = 128 + } + if l+8 > len(b) { + return nil, -1, errNoExtension + } + if !validExtensionHeader(b[l:]) { + if l == 128 { + return nil, -1, errNoExtension + } + l = 128 + if !validExtensionHeader(b[l:]) { + return nil, -1, errNoExtension + } + } + } + var exts []Extension + for b = b[l+4:]; len(b) >= 4; { + ol := int(binary.BigEndian.Uint16(b[:2])) + if 4 > ol || ol > len(b) { + break + } + switch b[2] { + case classMPLSLabelStack: + ext, err := parseMPLSLabelStack(b[:ol]) + if err != nil { + return nil, -1, err + } + exts = append(exts, ext) + case classInterfaceInfo: + ext, err := parseInterfaceInfo(b[:ol]) + if err != nil { + return nil, -1, err + } + exts = append(exts, ext) + case classInterfaceIdent: + ext, err := parseInterfaceIdent(b[:ol]) + if err != nil { + return nil, -1, err + } + exts = append(exts, ext) + default: + ext := &RawExtension{Data: make([]byte, ol)} + copy(ext.Data, b[:ol]) + exts = append(exts, ext) + } + b = b[ol:] + } + return exts, l, nil +} + +func validExtensions(typ Type, exts []Extension) bool { + switch typ { + case ipv4.ICMPTypeDestinationUnreachable, ipv4.ICMPTypeTimeExceeded, ipv4.ICMPTypeParameterProblem, + ipv6.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeTimeExceeded: + for i := range exts { + switch exts[i].(type) { + case *MPLSLabelStack, *InterfaceInfo, *RawExtension: + default: + return false + } + } + return true + case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest: + var n int + for i := range exts { + switch exts[i].(type) { + case *InterfaceIdent: + n++ + case *RawExtension: + default: + return false + } + } + // Not a single InterfaceIdent object or a combo of + // RawExtension and InterfaceIdent objects is not + // allowed. + if n == 1 && len(exts) > 1 { + return false + } + return true + default: + return false + } +} + +// A RawExtension represents a raw extension. +// +// A raw extension is excluded from message processing and can be used +// to construct applications such as protocol conformance testing. +type RawExtension struct { + Data []byte // data +} + +// Len implements the Len method of Extension interface. +func (p *RawExtension) Len(proto int) int { + if p == nil { + return 0 + } + return len(p.Data) +} + +// Marshal implements the Marshal method of Extension interface. +func (p *RawExtension) Marshal(proto int) ([]byte, error) { + return p.Data, nil +} diff --git a/vendor/golang.org/x/net/icmp/helper_posix.go b/vendor/golang.org/x/net/icmp/helper_posix.go new file mode 100644 index 0000000000..f625483f06 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/helper_posix.go @@ -0,0 +1,75 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows + +package icmp + +import ( + "net" + "strconv" + "syscall" +) + +func sockaddr(family int, address string) (syscall.Sockaddr, error) { + switch family { + case syscall.AF_INET: + a, err := net.ResolveIPAddr("ip4", address) + if err != nil { + return nil, err + } + if len(a.IP) == 0 { + a.IP = net.IPv4zero + } + if a.IP = a.IP.To4(); a.IP == nil { + return nil, net.InvalidAddrError("non-ipv4 address") + } + sa := &syscall.SockaddrInet4{} + copy(sa.Addr[:], a.IP) + return sa, nil + case syscall.AF_INET6: + a, err := net.ResolveIPAddr("ip6", address) + if err != nil { + return nil, err + } + if len(a.IP) == 0 { + a.IP = net.IPv6unspecified + } + if a.IP.Equal(net.IPv4zero) { + a.IP = net.IPv6unspecified + } + if a.IP = a.IP.To16(); a.IP == nil || a.IP.To4() != nil { + return nil, net.InvalidAddrError("non-ipv6 address") + } + sa := &syscall.SockaddrInet6{ZoneId: zoneToUint32(a.Zone)} + copy(sa.Addr[:], a.IP) + return sa, nil + default: + return nil, net.InvalidAddrError("unexpected family") + } +} + +func zoneToUint32(zone string) uint32 { + if zone == "" { + return 0 + } + if ifi, err := net.InterfaceByName(zone); err == nil { + return uint32(ifi.Index) + } + n, err := strconv.Atoi(zone) + if err != nil { + return 0 + } + return uint32(n) +} + +func last(s string, b byte) int { + i := len(s) + for i--; i >= 0; i-- { + if s[i] == b { + break + } + } + return i +} diff --git a/vendor/golang.org/x/net/icmp/interface.go b/vendor/golang.org/x/net/icmp/interface.go new file mode 100644 index 0000000000..b3dd72fb0a --- /dev/null +++ b/vendor/golang.org/x/net/icmp/interface.go @@ -0,0 +1,322 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + "net" + "strings" + + "golang.org/x/net/internal/iana" +) + +const ( + classInterfaceInfo = 2 +) + +const ( + attrMTU = 1 << iota + attrName + attrIPAddr + attrIfIndex +) + +// An InterfaceInfo represents interface and next-hop identification. +type InterfaceInfo struct { + Class int // extension object class number + Type int // extension object sub-type + Interface *net.Interface + Addr *net.IPAddr +} + +func (ifi *InterfaceInfo) nameLen() int { + if len(ifi.Interface.Name) > 63 { + return 64 + } + l := 1 + len(ifi.Interface.Name) + return (l + 3) &^ 3 +} + +func (ifi *InterfaceInfo) attrsAndLen(proto int) (attrs, l int) { + l = 4 + if ifi.Interface != nil && ifi.Interface.Index > 0 { + attrs |= attrIfIndex + l += 4 + if len(ifi.Interface.Name) > 0 { + attrs |= attrName + l += ifi.nameLen() + } + if ifi.Interface.MTU > 0 { + attrs |= attrMTU + l += 4 + } + } + if ifi.Addr != nil { + switch proto { + case iana.ProtocolICMP: + if ifi.Addr.IP.To4() != nil { + attrs |= attrIPAddr + l += 4 + net.IPv4len + } + case iana.ProtocolIPv6ICMP: + if ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil { + attrs |= attrIPAddr + l += 4 + net.IPv6len + } + } + } + return +} + +// Len implements the Len method of Extension interface. +func (ifi *InterfaceInfo) Len(proto int) int { + _, l := ifi.attrsAndLen(proto) + return l +} + +// Marshal implements the Marshal method of Extension interface. +func (ifi *InterfaceInfo) Marshal(proto int) ([]byte, error) { + attrs, l := ifi.attrsAndLen(proto) + b := make([]byte, l) + if err := ifi.marshal(proto, b, attrs, l); err != nil { + return nil, err + } + return b, nil +} + +func (ifi *InterfaceInfo) marshal(proto int, b []byte, attrs, l int) error { + binary.BigEndian.PutUint16(b[:2], uint16(l)) + b[2], b[3] = classInterfaceInfo, byte(ifi.Type) + for b = b[4:]; len(b) > 0 && attrs != 0; { + switch { + case attrs&attrIfIndex != 0: + b = ifi.marshalIfIndex(proto, b) + attrs &^= attrIfIndex + case attrs&attrIPAddr != 0: + b = ifi.marshalIPAddr(proto, b) + attrs &^= attrIPAddr + case attrs&attrName != 0: + b = ifi.marshalName(proto, b) + attrs &^= attrName + case attrs&attrMTU != 0: + b = ifi.marshalMTU(proto, b) + attrs &^= attrMTU + } + } + return nil +} + +func (ifi *InterfaceInfo) marshalIfIndex(proto int, b []byte) []byte { + binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.Index)) + return b[4:] +} + +func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + ifi.Interface.Index = int(binary.BigEndian.Uint32(b[:4])) + return b[4:], nil +} + +func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte { + switch proto { + case iana.ProtocolICMP: + binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv4)) + copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4()) + b = b[4+net.IPv4len:] + case iana.ProtocolIPv6ICMP: + binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv6)) + copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16()) + b = b[4+net.IPv6len:] + } + return b +} + +func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + afi := int(binary.BigEndian.Uint16(b[:2])) + b = b[4:] + switch afi { + case iana.AddrFamilyIPv4: + if len(b) < net.IPv4len { + return nil, errMessageTooShort + } + ifi.Addr.IP = make(net.IP, net.IPv4len) + copy(ifi.Addr.IP, b[:net.IPv4len]) + b = b[net.IPv4len:] + case iana.AddrFamilyIPv6: + if len(b) < net.IPv6len { + return nil, errMessageTooShort + } + ifi.Addr.IP = make(net.IP, net.IPv6len) + copy(ifi.Addr.IP, b[:net.IPv6len]) + b = b[net.IPv6len:] + } + return b, nil +} + +func (ifi *InterfaceInfo) marshalName(proto int, b []byte) []byte { + l := byte(ifi.nameLen()) + b[0] = l + copy(b[1:], []byte(ifi.Interface.Name)) + return b[l:] +} + +func (ifi *InterfaceInfo) parseName(b []byte) ([]byte, error) { + if 4 > len(b) || len(b) < int(b[0]) { + return nil, errMessageTooShort + } + l := int(b[0]) + if l%4 != 0 || 4 > l || l > 64 { + return nil, errInvalidExtension + } + var name [63]byte + copy(name[:], b[1:l]) + ifi.Interface.Name = strings.Trim(string(name[:]), "\000") + return b[l:], nil +} + +func (ifi *InterfaceInfo) marshalMTU(proto int, b []byte) []byte { + binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.MTU)) + return b[4:] +} + +func (ifi *InterfaceInfo) parseMTU(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + ifi.Interface.MTU = int(binary.BigEndian.Uint32(b[:4])) + return b[4:], nil +} + +func parseInterfaceInfo(b []byte) (Extension, error) { + ifi := &InterfaceInfo{ + Class: int(b[2]), + Type: int(b[3]), + } + if ifi.Type&(attrIfIndex|attrName|attrMTU) != 0 { + ifi.Interface = &net.Interface{} + } + if ifi.Type&attrIPAddr != 0 { + ifi.Addr = &net.IPAddr{} + } + attrs := ifi.Type & (attrIfIndex | attrIPAddr | attrName | attrMTU) + for b = b[4:]; len(b) > 0 && attrs != 0; { + var err error + switch { + case attrs&attrIfIndex != 0: + b, err = ifi.parseIfIndex(b) + attrs &^= attrIfIndex + case attrs&attrIPAddr != 0: + b, err = ifi.parseIPAddr(b) + attrs &^= attrIPAddr + case attrs&attrName != 0: + b, err = ifi.parseName(b) + attrs &^= attrName + case attrs&attrMTU != 0: + b, err = ifi.parseMTU(b) + attrs &^= attrMTU + } + if err != nil { + return nil, err + } + } + if ifi.Interface != nil && ifi.Interface.Name != "" && ifi.Addr != nil && ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil { + ifi.Addr.Zone = ifi.Interface.Name + } + return ifi, nil +} + +const ( + classInterfaceIdent = 3 + typeInterfaceByName = 1 + typeInterfaceByIndex = 2 + typeInterfaceByAddress = 3 +) + +// An InterfaceIdent represents interface identification. +type InterfaceIdent struct { + Class int // extension object class number + Type int // extension object sub-type + Name string // interface name + Index int // interface index + AFI int // address family identifier; see address family numbers in IANA registry + Addr []byte // address +} + +// Len implements the Len method of Extension interface. +func (ifi *InterfaceIdent) Len(_ int) int { + switch ifi.Type { + case typeInterfaceByName: + l := len(ifi.Name) + if l > 255 { + l = 255 + } + return 4 + (l+3)&^3 + case typeInterfaceByIndex: + return 4 + 4 + case typeInterfaceByAddress: + return 4 + 4 + (len(ifi.Addr)+3)&^3 + default: + return 4 + } +} + +// Marshal implements the Marshal method of Extension interface. +func (ifi *InterfaceIdent) Marshal(proto int) ([]byte, error) { + b := make([]byte, ifi.Len(proto)) + if err := ifi.marshal(proto, b); err != nil { + return nil, err + } + return b, nil +} + +func (ifi *InterfaceIdent) marshal(proto int, b []byte) error { + l := ifi.Len(proto) + binary.BigEndian.PutUint16(b[:2], uint16(l)) + b[2], b[3] = classInterfaceIdent, byte(ifi.Type) + switch ifi.Type { + case typeInterfaceByName: + copy(b[4:], ifi.Name) + case typeInterfaceByIndex: + binary.BigEndian.PutUint32(b[4:4+4], uint32(ifi.Index)) + case typeInterfaceByAddress: + binary.BigEndian.PutUint16(b[4:4+2], uint16(ifi.AFI)) + b[4+2] = byte(len(ifi.Addr)) + copy(b[4+4:], ifi.Addr) + } + return nil +} + +func parseInterfaceIdent(b []byte) (Extension, error) { + ifi := &InterfaceIdent{ + Class: int(b[2]), + Type: int(b[3]), + } + switch ifi.Type { + case typeInterfaceByName: + ifi.Name = strings.Trim(string(b[4:]), "\x00") + case typeInterfaceByIndex: + if len(b[4:]) < 4 { + return nil, errInvalidExtension + } + ifi.Index = int(binary.BigEndian.Uint32(b[4 : 4+4])) + case typeInterfaceByAddress: + if len(b[4:]) < 4 { + return nil, errInvalidExtension + } + ifi.AFI = int(binary.BigEndian.Uint16(b[4 : 4+2])) + l := int(b[4+2]) + if len(b[4+4:]) < l { + return nil, errInvalidExtension + } + ifi.Addr = make([]byte, l) + copy(ifi.Addr, b[4+4:]) + } + return ifi, nil +} diff --git a/vendor/golang.org/x/net/icmp/ipv4.go b/vendor/golang.org/x/net/icmp/ipv4.go new file mode 100644 index 0000000000..0ad40fef2f --- /dev/null +++ b/vendor/golang.org/x/net/icmp/ipv4.go @@ -0,0 +1,69 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + "net" + "runtime" + + "golang.org/x/net/internal/socket" + "golang.org/x/net/ipv4" +) + +// freebsdVersion is set in sys_freebsd.go. +// See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html. +var freebsdVersion uint32 + +// ParseIPv4Header returns the IPv4 header of the IPv4 packet that +// triggered an ICMP error message. +// This is found in the Data field of the ICMP error message body. +// +// The provided b must be in the format used by a raw ICMP socket on +// the local system. +// This may differ from the wire format, and the format used by a raw +// IP socket, depending on the system. +// +// To parse an IPv6 header, use ipv6.ParseHeader. +func ParseIPv4Header(b []byte) (*ipv4.Header, error) { + if len(b) < ipv4.HeaderLen { + return nil, errHeaderTooShort + } + hdrlen := int(b[0]&0x0f) << 2 + if hdrlen > len(b) { + return nil, errBufferTooShort + } + h := &ipv4.Header{ + Version: int(b[0] >> 4), + Len: hdrlen, + TOS: int(b[1]), + ID: int(binary.BigEndian.Uint16(b[4:6])), + FragOff: int(binary.BigEndian.Uint16(b[6:8])), + TTL: int(b[8]), + Protocol: int(b[9]), + Checksum: int(binary.BigEndian.Uint16(b[10:12])), + Src: net.IPv4(b[12], b[13], b[14], b[15]), + Dst: net.IPv4(b[16], b[17], b[18], b[19]), + } + switch runtime.GOOS { + case "darwin", "ios": + h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + case "freebsd": + if freebsdVersion >= 1000000 { + h.TotalLen = int(binary.BigEndian.Uint16(b[2:4])) + } else { + h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + } + default: + h.TotalLen = int(binary.BigEndian.Uint16(b[2:4])) + } + h.Flags = ipv4.HeaderFlags(h.FragOff&0xe000) >> 13 + h.FragOff = h.FragOff & 0x1fff + if hdrlen-ipv4.HeaderLen > 0 { + h.Options = make([]byte, hdrlen-ipv4.HeaderLen) + copy(h.Options, b[ipv4.HeaderLen:]) + } + return h, nil +} diff --git a/vendor/golang.org/x/net/icmp/ipv6.go b/vendor/golang.org/x/net/icmp/ipv6.go new file mode 100644 index 0000000000..2e8cfeb131 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/ipv6.go @@ -0,0 +1,23 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "net" + + "golang.org/x/net/internal/iana" +) + +const ipv6PseudoHeaderLen = 2*net.IPv6len + 8 + +// IPv6PseudoHeader returns an IPv6 pseudo header for checksum +// calculation. +func IPv6PseudoHeader(src, dst net.IP) []byte { + b := make([]byte, ipv6PseudoHeaderLen) + copy(b, src.To16()) + copy(b[net.IPv6len:], dst.To16()) + b[len(b)-1] = byte(iana.ProtocolIPv6ICMP) + return b +} diff --git a/vendor/golang.org/x/net/icmp/listen_posix.go b/vendor/golang.org/x/net/icmp/listen_posix.go new file mode 100644 index 0000000000..b7cb15b7dc --- /dev/null +++ b/vendor/golang.org/x/net/icmp/listen_posix.go @@ -0,0 +1,105 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows + +package icmp + +import ( + "net" + "os" + "runtime" + "syscall" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +const sysIP_STRIPHDR = 0x17 // for now only darwin supports this option + +// ListenPacket listens for incoming ICMP packets addressed to +// address. See net.Dial for the syntax of address. +// +// For non-privileged datagram-oriented ICMP endpoints, network must +// be "udp4" or "udp6". The endpoint allows to read, write a few +// limited ICMP messages such as echo request and echo reply. +// Currently only Darwin and Linux support this. +// +// Examples: +// +// ListenPacket("udp4", "192.168.0.1") +// ListenPacket("udp4", "0.0.0.0") +// ListenPacket("udp6", "fe80::1%en0") +// ListenPacket("udp6", "::") +// +// For privileged raw ICMP endpoints, network must be "ip4" or "ip6" +// followed by a colon and an ICMP protocol number or name. +// +// Examples: +// +// ListenPacket("ip4:icmp", "192.168.0.1") +// ListenPacket("ip4:1", "0.0.0.0") +// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0") +// ListenPacket("ip6:58", "::") +func ListenPacket(network, address string) (*PacketConn, error) { + var family, proto int + switch network { + case "udp4": + family, proto = syscall.AF_INET, iana.ProtocolICMP + case "udp6": + family, proto = syscall.AF_INET6, iana.ProtocolIPv6ICMP + default: + i := last(network, ':') + if i < 0 { + i = len(network) + } + switch network[:i] { + case "ip4": + proto = iana.ProtocolICMP + case "ip6": + proto = iana.ProtocolIPv6ICMP + } + } + var cerr error + var c net.PacketConn + switch family { + case syscall.AF_INET, syscall.AF_INET6: + s, err := syscall.Socket(family, syscall.SOCK_DGRAM, proto) + if err != nil { + return nil, os.NewSyscallError("socket", err) + } + if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && family == syscall.AF_INET { + if err := syscall.SetsockoptInt(s, iana.ProtocolIP, sysIP_STRIPHDR, 1); err != nil { + syscall.Close(s) + return nil, os.NewSyscallError("setsockopt", err) + } + } + sa, err := sockaddr(family, address) + if err != nil { + syscall.Close(s) + return nil, err + } + if err := syscall.Bind(s, sa); err != nil { + syscall.Close(s) + return nil, os.NewSyscallError("bind", err) + } + f := os.NewFile(uintptr(s), "datagram-oriented icmp") + c, cerr = net.FilePacketConn(f) + f.Close() + default: + c, cerr = net.ListenPacket(network, address) + } + if cerr != nil { + return nil, cerr + } + switch proto { + case iana.ProtocolICMP: + return &PacketConn{c: c, p4: ipv4.NewPacketConn(c)}, nil + case iana.ProtocolIPv6ICMP: + return &PacketConn{c: c, p6: ipv6.NewPacketConn(c)}, nil + default: + return &PacketConn{c: c}, nil + } +} diff --git a/vendor/golang.org/x/net/icmp/listen_stub.go b/vendor/golang.org/x/net/icmp/listen_stub.go new file mode 100644 index 0000000000..7b76be1cb3 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/listen_stub.go @@ -0,0 +1,35 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows + +package icmp + +// ListenPacket listens for incoming ICMP packets addressed to +// address. See net.Dial for the syntax of address. +// +// For non-privileged datagram-oriented ICMP endpoints, network must +// be "udp4" or "udp6". The endpoint allows to read, write a few +// limited ICMP messages such as echo request and echo reply. +// Currently only Darwin and Linux support this. +// +// Examples: +// +// ListenPacket("udp4", "192.168.0.1") +// ListenPacket("udp4", "0.0.0.0") +// ListenPacket("udp6", "fe80::1%en0") +// ListenPacket("udp6", "::") +// +// For privileged raw ICMP endpoints, network must be "ip4" or "ip6" +// followed by a colon and an ICMP protocol number or name. +// +// Examples: +// +// ListenPacket("ip4:icmp", "192.168.0.1") +// ListenPacket("ip4:1", "0.0.0.0") +// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0") +// ListenPacket("ip6:58", "::") +func ListenPacket(network, address string) (*PacketConn, error) { + return nil, errNotImplemented +} diff --git a/vendor/golang.org/x/net/icmp/message.go b/vendor/golang.org/x/net/icmp/message.go new file mode 100644 index 0000000000..40db65d0cd --- /dev/null +++ b/vendor/golang.org/x/net/icmp/message.go @@ -0,0 +1,162 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package icmp provides basic functions for the manipulation of +// messages used in the Internet Control Message Protocols, +// ICMPv4 and ICMPv6. +// +// ICMPv4 and ICMPv6 are defined in RFC 792 and RFC 4443. +// Multi-part message support for ICMP is defined in RFC 4884. +// ICMP extensions for MPLS are defined in RFC 4950. +// ICMP extensions for interface and next-hop identification are +// defined in RFC 5837. +// PROBE: A utility for probing interfaces is defined in RFC 8335. +package icmp // import "golang.org/x/net/icmp" + +import ( + "encoding/binary" + "errors" + "net" + "runtime" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// BUG(mikio): This package is not implemented on JS, NaCl and Plan 9. + +var ( + errInvalidConn = errors.New("invalid connection") + errInvalidProtocol = errors.New("invalid protocol") + errMessageTooShort = errors.New("message too short") + errHeaderTooShort = errors.New("header too short") + errBufferTooShort = errors.New("buffer too short") + errInvalidBody = errors.New("invalid body") + errNoExtension = errors.New("no extension") + errInvalidExtension = errors.New("invalid extension") + errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH) +) + +func checksum(b []byte) uint16 { + csumcv := len(b) - 1 // checksum coverage + s := uint32(0) + for i := 0; i < csumcv; i += 2 { + s += uint32(b[i+1])<<8 | uint32(b[i]) + } + if csumcv&1 == 0 { + s += uint32(b[csumcv]) + } + s = s>>16 + s&0xffff + s = s + s>>16 + return ^uint16(s) +} + +// A Type represents an ICMP message type. +type Type interface { + Protocol() int +} + +// A Message represents an ICMP message. +type Message struct { + Type Type // type, either ipv4.ICMPType or ipv6.ICMPType + Code int // code + Checksum int // checksum + Body MessageBody // body +} + +// Marshal returns the binary encoding of the ICMP message m. +// +// For an ICMPv4 message, the returned message always contains the +// calculated checksum field. +// +// For an ICMPv6 message, the returned message contains the calculated +// checksum field when psh is not nil, otherwise the kernel will +// compute the checksum field during the message transmission. +// When psh is not nil, it must be the pseudo header for IPv6. +func (m *Message) Marshal(psh []byte) ([]byte, error) { + var mtype byte + switch typ := m.Type.(type) { + case ipv4.ICMPType: + mtype = byte(typ) + case ipv6.ICMPType: + mtype = byte(typ) + default: + return nil, errInvalidProtocol + } + b := []byte{mtype, byte(m.Code), 0, 0} + proto := m.Type.Protocol() + if proto == iana.ProtocolIPv6ICMP && psh != nil { + b = append(psh, b...) + } + if m.Body != nil && m.Body.Len(proto) != 0 { + mb, err := m.Body.Marshal(proto) + if err != nil { + return nil, err + } + b = append(b, mb...) + } + if proto == iana.ProtocolIPv6ICMP { + if psh == nil { // cannot calculate checksum here + return b, nil + } + off, l := 2*net.IPv6len, len(b)-len(psh) + binary.BigEndian.PutUint32(b[off:off+4], uint32(l)) + } + s := checksum(b) + // Place checksum back in header; using ^= avoids the + // assumption the checksum bytes are zero. + b[len(psh)+2] ^= byte(s) + b[len(psh)+3] ^= byte(s >> 8) + return b[len(psh):], nil +} + +var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){ + ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach, + ipv4.ICMPTypeTimeExceeded: parseTimeExceeded, + ipv4.ICMPTypeParameterProblem: parseParamProb, + + ipv4.ICMPTypeEcho: parseEcho, + ipv4.ICMPTypeEchoReply: parseEcho, + ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest, + ipv4.ICMPTypeExtendedEchoReply: parseExtendedEchoReply, + + ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach, + ipv6.ICMPTypePacketTooBig: parsePacketTooBig, + ipv6.ICMPTypeTimeExceeded: parseTimeExceeded, + ipv6.ICMPTypeParameterProblem: parseParamProb, + + ipv6.ICMPTypeEchoRequest: parseEcho, + ipv6.ICMPTypeEchoReply: parseEcho, + ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest, + ipv6.ICMPTypeExtendedEchoReply: parseExtendedEchoReply, +} + +// ParseMessage parses b as an ICMP message. +// The provided proto must be either the ICMPv4 or ICMPv6 protocol +// number. +func ParseMessage(proto int, b []byte) (*Message, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + var err error + m := &Message{Code: int(b[1]), Checksum: int(binary.BigEndian.Uint16(b[2:4]))} + switch proto { + case iana.ProtocolICMP: + m.Type = ipv4.ICMPType(b[0]) + case iana.ProtocolIPv6ICMP: + m.Type = ipv6.ICMPType(b[0]) + default: + return nil, errInvalidProtocol + } + if fn, ok := parseFns[m.Type]; !ok { + m.Body, err = parseRawBody(proto, b[4:]) + } else { + m.Body, err = fn(proto, m.Type, b[4:]) + } + if err != nil { + return nil, err + } + return m, nil +} diff --git a/vendor/golang.org/x/net/icmp/messagebody.go b/vendor/golang.org/x/net/icmp/messagebody.go new file mode 100644 index 0000000000..e2d9bfa01b --- /dev/null +++ b/vendor/golang.org/x/net/icmp/messagebody.go @@ -0,0 +1,52 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +// A MessageBody represents an ICMP message body. +type MessageBody interface { + // Len returns the length of ICMP message body. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Len(proto int) int + + // Marshal returns the binary encoding of ICMP message body. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Marshal(proto int) ([]byte, error) +} + +// A RawBody represents a raw message body. +// +// A raw message body is excluded from message processing and can be +// used to construct applications such as protocol conformance +// testing. +type RawBody struct { + Data []byte // data +} + +// Len implements the Len method of MessageBody interface. +func (p *RawBody) Len(proto int) int { + if p == nil { + return 0 + } + return len(p.Data) +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *RawBody) Marshal(proto int) ([]byte, error) { + return p.Data, nil +} + +// parseRawBody parses b as an ICMP message body. +func parseRawBody(proto int, b []byte) (MessageBody, error) { + p := &RawBody{Data: make([]byte, len(b))} + copy(p.Data, b) + return p, nil +} + +// A DefaultMessageBody represents the default message body. +// +// Deprecated: Use RawBody instead. +type DefaultMessageBody = RawBody diff --git a/vendor/golang.org/x/net/icmp/mpls.go b/vendor/golang.org/x/net/icmp/mpls.go new file mode 100644 index 0000000000..f9f4841bce --- /dev/null +++ b/vendor/golang.org/x/net/icmp/mpls.go @@ -0,0 +1,77 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "encoding/binary" + +// MPLSLabel represents an MPLS label stack entry. +type MPLSLabel struct { + Label int // label value + TC int // traffic class; formerly experimental use + S bool // bottom of stack + TTL int // time to live +} + +const ( + classMPLSLabelStack = 1 + typeIncomingMPLSLabelStack = 1 +) + +// MPLSLabelStack represents an MPLS label stack. +type MPLSLabelStack struct { + Class int // extension object class number + Type int // extension object sub-type + Labels []MPLSLabel +} + +// Len implements the Len method of Extension interface. +func (ls *MPLSLabelStack) Len(proto int) int { + return 4 + (4 * len(ls.Labels)) +} + +// Marshal implements the Marshal method of Extension interface. +func (ls *MPLSLabelStack) Marshal(proto int) ([]byte, error) { + b := make([]byte, ls.Len(proto)) + if err := ls.marshal(proto, b); err != nil { + return nil, err + } + return b, nil +} + +func (ls *MPLSLabelStack) marshal(proto int, b []byte) error { + l := ls.Len(proto) + binary.BigEndian.PutUint16(b[:2], uint16(l)) + b[2], b[3] = classMPLSLabelStack, typeIncomingMPLSLabelStack + off := 4 + for _, ll := range ls.Labels { + b[off], b[off+1], b[off+2] = byte(ll.Label>>12), byte(ll.Label>>4&0xff), byte(ll.Label<<4&0xf0) + b[off+2] |= byte(ll.TC << 1 & 0x0e) + if ll.S { + b[off+2] |= 0x1 + } + b[off+3] = byte(ll.TTL) + off += 4 + } + return nil +} + +func parseMPLSLabelStack(b []byte) (Extension, error) { + ls := &MPLSLabelStack{ + Class: int(b[2]), + Type: int(b[3]), + } + for b = b[4:]; len(b) >= 4; b = b[4:] { + ll := MPLSLabel{ + Label: int(b[0])<<12 | int(b[1])<<4 | int(b[2])>>4, + TC: int(b[2]&0x0e) >> 1, + TTL: int(b[3]), + } + if b[2]&0x1 != 0 { + ll.S = true + } + ls.Labels = append(ls.Labels, ll) + } + return ls, nil +} diff --git a/vendor/golang.org/x/net/icmp/multipart.go b/vendor/golang.org/x/net/icmp/multipart.go new file mode 100644 index 0000000000..c7b72bf3dd --- /dev/null +++ b/vendor/golang.org/x/net/icmp/multipart.go @@ -0,0 +1,129 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "golang.org/x/net/internal/iana" + +// multipartMessageBodyDataLen takes b as an original datagram and +// exts as extensions, and returns a required length for message body +// and a required length for a padded original datagram in wire +// format. +func multipartMessageBodyDataLen(proto int, withOrigDgram bool, b []byte, exts []Extension) (bodyLen, dataLen int) { + bodyLen = 4 // length of leading octets + var extLen int + var rawExt bool // raw extension may contain an empty object + for _, ext := range exts { + extLen += ext.Len(proto) + if _, ok := ext.(*RawExtension); ok { + rawExt = true + } + } + if extLen > 0 && withOrigDgram { + dataLen = multipartMessageOrigDatagramLen(proto, b) + } else { + dataLen = len(b) + } + if extLen > 0 || rawExt { + bodyLen += 4 // length of extension header + } + bodyLen += dataLen + extLen + return bodyLen, dataLen +} + +// multipartMessageOrigDatagramLen takes b as an original datagram, +// and returns a required length for a padded original datagram in wire +// format. +func multipartMessageOrigDatagramLen(proto int, b []byte) int { + roundup := func(b []byte, align int) int { + // According to RFC 4884, the padded original datagram + // field must contain at least 128 octets. + if len(b) < 128 { + return 128 + } + r := len(b) + return (r + align - 1) &^ (align - 1) + } + switch proto { + case iana.ProtocolICMP: + return roundup(b, 4) + case iana.ProtocolIPv6ICMP: + return roundup(b, 8) + default: + return len(b) + } +} + +// marshalMultipartMessageBody takes data as an original datagram and +// exts as extesnsions, and returns a binary encoding of message body. +// It can be used for non-multipart message bodies when exts is nil. +func marshalMultipartMessageBody(proto int, withOrigDgram bool, data []byte, exts []Extension) ([]byte, error) { + bodyLen, dataLen := multipartMessageBodyDataLen(proto, withOrigDgram, data, exts) + b := make([]byte, bodyLen) + copy(b[4:], data) + if len(exts) > 0 { + b[4+dataLen] = byte(extensionVersion << 4) + off := 4 + dataLen + 4 // leading octets, data, extension header + for _, ext := range exts { + switch ext := ext.(type) { + case *MPLSLabelStack: + if err := ext.marshal(proto, b[off:]); err != nil { + return nil, err + } + off += ext.Len(proto) + case *InterfaceInfo: + attrs, l := ext.attrsAndLen(proto) + if err := ext.marshal(proto, b[off:], attrs, l); err != nil { + return nil, err + } + off += ext.Len(proto) + case *InterfaceIdent: + if err := ext.marshal(proto, b[off:]); err != nil { + return nil, err + } + off += ext.Len(proto) + case *RawExtension: + copy(b[off:], ext.Data) + off += ext.Len(proto) + } + } + s := checksum(b[4+dataLen:]) + b[4+dataLen+2] ^= byte(s) + b[4+dataLen+3] ^= byte(s >> 8) + if withOrigDgram { + switch proto { + case iana.ProtocolICMP: + b[1] = byte(dataLen / 4) + case iana.ProtocolIPv6ICMP: + b[0] = byte(dataLen / 8) + } + } + } + return b, nil +} + +// parseMultipartMessageBody parses b as either a non-multipart +// message body or a multipart message body. +func parseMultipartMessageBody(proto int, typ Type, b []byte) ([]byte, []Extension, error) { + var l int + switch proto { + case iana.ProtocolICMP: + l = 4 * int(b[1]) + case iana.ProtocolIPv6ICMP: + l = 8 * int(b[0]) + } + if len(b) == 4 { + return nil, nil, nil + } + exts, l, err := parseExtensions(typ, b[4:], l) + if err != nil { + l = len(b) - 4 + } + var data []byte + if l > 0 { + data = make([]byte, l) + copy(data, b[4:]) + } + return data, exts, nil +} diff --git a/vendor/golang.org/x/net/icmp/packettoobig.go b/vendor/golang.org/x/net/icmp/packettoobig.go new file mode 100644 index 0000000000..afbf24f1ba --- /dev/null +++ b/vendor/golang.org/x/net/icmp/packettoobig.go @@ -0,0 +1,43 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "encoding/binary" + +// A PacketTooBig represents an ICMP packet too big message body. +type PacketTooBig struct { + MTU int // maximum transmission unit of the nexthop link + Data []byte // data, known as original datagram field +} + +// Len implements the Len method of MessageBody interface. +func (p *PacketTooBig) Len(proto int) int { + if p == nil { + return 0 + } + return 4 + len(p.Data) +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *PacketTooBig) Marshal(proto int) ([]byte, error) { + b := make([]byte, 4+len(p.Data)) + binary.BigEndian.PutUint32(b[:4], uint32(p.MTU)) + copy(b[4:], p.Data) + return b, nil +} + +// parsePacketTooBig parses b as an ICMP packet too big message body. +func parsePacketTooBig(proto int, _ Type, b []byte) (MessageBody, error) { + bodyLen := len(b) + if bodyLen < 4 { + return nil, errMessageTooShort + } + p := &PacketTooBig{MTU: int(binary.BigEndian.Uint32(b[:4]))} + if bodyLen > 4 { + p.Data = make([]byte, bodyLen-4) + copy(p.Data, b[4:]) + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/paramprob.go b/vendor/golang.org/x/net/icmp/paramprob.go new file mode 100644 index 0000000000..f16fd33ec2 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/paramprob.go @@ -0,0 +1,72 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" +) + +// A ParamProb represents an ICMP parameter problem message body. +type ParamProb struct { + Pointer uintptr // offset within the data where the error was detected + Data []byte // data, known as original datagram field + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *ParamProb) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *ParamProb) Marshal(proto int) ([]byte, error) { + switch proto { + case iana.ProtocolICMP: + if !validExtensions(ipv4.ICMPTypeParameterProblem, p.Extensions) { + return nil, errInvalidExtension + } + b, err := marshalMultipartMessageBody(proto, true, p.Data, p.Extensions) + if err != nil { + return nil, err + } + b[0] = byte(p.Pointer) + return b, nil + case iana.ProtocolIPv6ICMP: + b := make([]byte, p.Len(proto)) + binary.BigEndian.PutUint32(b[:4], uint32(p.Pointer)) + copy(b[4:], p.Data) + return b, nil + default: + return nil, errInvalidProtocol + } +} + +// parseParamProb parses b as an ICMP parameter problem message body. +func parseParamProb(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &ParamProb{} + if proto == iana.ProtocolIPv6ICMP { + p.Pointer = uintptr(binary.BigEndian.Uint32(b[:4])) + p.Data = make([]byte, len(b)-4) + copy(p.Data, b[4:]) + return p, nil + } + p.Pointer = uintptr(b[0]) + var err error + p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/sys_freebsd.go b/vendor/golang.org/x/net/icmp/sys_freebsd.go new file mode 100644 index 0000000000..c75f3ddaa7 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/sys_freebsd.go @@ -0,0 +1,11 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "syscall" + +func init() { + freebsdVersion, _ = syscall.SysctlUint32("kern.osreldate") +} diff --git a/vendor/golang.org/x/net/icmp/timeexceeded.go b/vendor/golang.org/x/net/icmp/timeexceeded.go new file mode 100644 index 0000000000..ffa986fdea --- /dev/null +++ b/vendor/golang.org/x/net/icmp/timeexceeded.go @@ -0,0 +1,57 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// A TimeExceeded represents an ICMP time exceeded message body. +type TimeExceeded struct { + Data []byte // data, known as original datagram field + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *TimeExceeded) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *TimeExceeded) Marshal(proto int) ([]byte, error) { + var typ Type + switch proto { + case iana.ProtocolICMP: + typ = ipv4.ICMPTypeTimeExceeded + case iana.ProtocolIPv6ICMP: + typ = ipv6.ICMPTypeTimeExceeded + default: + return nil, errInvalidProtocol + } + if !validExtensions(typ, p.Extensions) { + return nil, errInvalidExtension + } + return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions) +} + +// parseTimeExceeded parses b as an ICMP time exceeded message body. +func parseTimeExceeded(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &TimeExceeded{} + var err error + p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4fcea8b07c..cce2985375 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -67,7 +67,7 @@ github.com/charmbracelet/bubbletea # github.com/charmbracelet/lipgloss v0.7.1 ## explicit; go 1.17 github.com/charmbracelet/lipgloss -# github.com/cloudflare/circl v1.3.3 +# github.com/cloudflare/circl v1.3.7 ## explicit; go 1.19 github.com/cloudflare/circl/dh/x25519 github.com/cloudflare/circl/dh/x448 @@ -127,15 +127,15 @@ github.com/hashicorp/errwrap # github.com/hashicorp/go-multierror v1.1.1 ## explicit; go 1.13 github.com/hashicorp/go-multierror -# github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f -## explicit; go 1.18 +# github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa +## explicit; go 1.21 github.com/hugelgupf/vmtest github.com/hugelgupf/vmtest/guest github.com/hugelgupf/vmtest/internal/eventchannel github.com/hugelgupf/vmtest/internal/json2test github.com/hugelgupf/vmtest/internal/testevent github.com/hugelgupf/vmtest/qemu -github.com/hugelgupf/vmtest/qemu/network +github.com/hugelgupf/vmtest/qemu/qnetwork github.com/hugelgupf/vmtest/testtmp github.com/hugelgupf/vmtest/uqemu github.com/hugelgupf/vmtest/vminit/gouinit @@ -279,11 +279,12 @@ github.com/u-root/gobusybox/src/pkg/uflag # github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a ## explicit; go 1.13 github.com/u-root/iscsinl -# github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 +# github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e ## explicit; go 1.16 github.com/u-root/uio/cp github.com/u-root/uio/rand github.com/u-root/uio/uio +github.com/u-root/uio/uio/uiotest github.com/u-root/uio/ulog github.com/u-root/uio/ulog/ulogtest # github.com/ulikunitz/xz v0.5.11 @@ -333,6 +334,7 @@ golang.org/x/mod/semver # golang.org/x/net v0.19.0 ## explicit; go 1.18 golang.org/x/net/bpf +golang.org/x/net/icmp golang.org/x/net/internal/iana golang.org/x/net/internal/socket golang.org/x/net/ipv4