Added cubietruck support

...along with tons of stuff that got accumulated within the last year.

Should have been more disciplined, but somehow did not have time to
properly maintain it beyond "works for me".
This commit is contained in:
Sergey 'Jin' Bostandzhyan
2021-06-12 01:24:57 +02:00
parent b2bdf1e00b
commit 6926dc8100
57 changed files with 8158 additions and 16 deletions

6
.gitmodules vendored
View File

@@ -13,3 +13,9 @@
[submodule "yocto/openembedded-core"]
path = yocto/openembedded-core
url = git://git.openembedded.org/openembedded-core
[submodule "yocto/meta-sunxi"]
path = yocto/meta-sunxi
url = https://github.com/linux-sunxi/meta-sunxi
[submodule "yocto/meta-arm"]
path = yocto/meta-arm
url = git://git.yoctoproject.org/meta-arm

View File

@@ -13,14 +13,14 @@ export BUILDDIR=${YOCTO_TOPDIR}/${TARGET_CONFIG}
export DL_DIR=${YOCTO_TOPDIR}/downloads
export BB_ENV_EXTRAWHITE="YOCTO_TOPDIR DL_DIR BUILDDIR PYTHONPATH"
export PATH=${YOCTO_TOPDIR}/yocto/poky/scripts:${YOCTO_TOPDIR}/yocto/poky/bitbake/bin:${PATH}
export PYTHONPATH=${YOCTO_TOPDIR}/yocto/poky/bitbake/lib
export PATH=${YOCTO_TOPDIR}/yocto/meta-klipper/scripts:${YOCTO_TOPDIR}/yocto/poky/scripts:${YOCTO_TOPDIR}/yocto/poky/bitbake/bin:${PATH}
export PYTHONPATH=${YOCTO_TOPDIR}/yocto/meta-klipper/scripts/lib:${YOCTO_TOPDIR}/yocto/poky/bitbake/lib
if [ ! -d /opt/poky/3.0 ]; then
echo "Please install http://downloads.yoctoproject.org/releases/yocto/yocto-3.0/buildtools/x86_64-buildtools-nativesdk-standalone-3.0.sh"
if [ ! -d /opt/poky/3.1.2 ]; then
echo "Please install http://downloads.yoctoproject.org/releases/yocto/yocto-3.1.2/buildtools/x86_64-buildtools-extended-nativesdk-standalone-3.1.2.sh"
else
source /opt/poky/3.0/environment-setup-x86_64-pokysdk-linux
export PYTHONPATH=$PYTHONPATH:/opt/poky/3.0/sysroots/x86_64-pokysdk-linux/usr/lib/python3.7/site-packages/
source /opt/poky/3.1.2/environment-setup-x86_64-pokysdk-linux
export PYTHONPATH=$PYTHONPATH:/opt/poky/3.1.2/sysroots/x86_64-pokysdk-linux/usr/lib/python3.8/site-packages/
fi
echo "Setup complete for $TARGET_CONFIG"

View File

@@ -0,0 +1,15 @@
LCONF_VERSION = "7"
BBLAYERS = " \
${YOCTO_TOPDIR}/yocto/poky/meta \
${YOCTO_TOPDIR}/yocto/poky/meta-poky \
${YOCTO_TOPDIR}/yocto/poky/meta-yocto-bsp \
${YOCTO_TOPDIR}/yocto/meta-openembedded/meta-oe \
${YOCTO_TOPDIR}/yocto/meta-openembedded/meta-python \
${YOCTO_TOPDIR}/yocto/meta-openembedded/meta-perl \
${YOCTO_TOPDIR}/yocto/meta-openembedded/meta-networking \
${YOCTO_TOPDIR}/yocto/meta-sunxi \
${YOCTO_TOPDIR}/yocto/meta-python2 \
${YOCTO_TOPDIR}/yocto/meta-klipper \
/home/jin/Work/oe/targets/cubietruck/workspace \
"

View File

@@ -0,0 +1,15 @@
DISTRO = "klipper-linux"
MACHINE = "cubietruck"
PRSERV_HOST = "localhost:0"
PACKAGE_CLASSES = " package_ipk "
INHERIT += "rm_work"
TOPDIR = "${YOCTO_TOPDIR}/${DISTRO}-build"
TMPDIR = "${TOPDIR}/build"
IMAGE_FEATURES += "debug-tweaks"
IMAGE_BOOT_FILES = "boot.scr sun7i-a20-cubietruck.dtb uImage"

View File

@@ -8,10 +8,11 @@ BBLAYERS = " \
${YOCTO_TOPDIR}/yocto/meta-openembedded/meta-python \
${YOCTO_TOPDIR}/yocto/meta-openembedded/meta-perl \
${YOCTO_TOPDIR}/yocto/meta-openembedded/meta-networking \
${YOCTO_TOPDIR}/yocto/meta-arm/meta-arm-toolchain \
${YOCTO_TOPDIR}/yocto/meta-arm/meta-arm \
${YOCTO_TOPDIR}/yocto/meta-rockchip \
${YOCTO_TOPDIR}/yocto/meta-python2 \
${YOCTO_TOPDIR}/yocto/meta-klipper \
"
BBMASK = "${YOCTO_TOPDIR}/yocto/meta-maker/recipes-python/werkzeug \
${YOCTO_TOPDIR}/yocto/meta-maker/recipes-python/jinja2"
BBMASK = "${YOCTO_TOPDIR}/yocto/meta-klipper/recipes-kernel/linux/linux-mainline_%.bbappend"

1
yocto/meta-arm Submodule

Submodule yocto/meta-arm added at d11487b64e

View File

@@ -9,3 +9,6 @@ VOLATILE_LOG_DIR = "no"
do_image_complete[deptask] += "do_populate_lic"
BB_NUMBER_PARSE_THREADS ?= "${@min(oe.utils.cpu_count(),3)}"
# util-linux-native fails to find magic.h
ASSUME_PROVIDED_remove = "file-native"

View File

@@ -0,0 +1,19 @@
CONFIG_NAND_BOOT=y
CONFIG_CMD_NAND=y
CONFIG_CMD_NAND_TRIMFFS=y
CONFIG_ENV_IS_IN_NAND=y
#CONFIG_ENV_ADDR=
CONFIG_MTD=y
CONFIG_MTD_RAW_NAND=y
CONFIG_SYS_NAND_SELF_INIT=y
CONFIG_SYS_NAND_USE_FLASH_BBT=y
CONFIG_NAND_SUNXI=y
CONFIG_NAND_SUNXI_SPL_ECC_STRENGTH=64
CONFIG_NAND_SUNXI_SPL_ECC_SIZE=1024
CONFIG_NAND_SUNXI_SPL_USABLE_PAGE_SIZE=1024
CONFIG_SYS_NAND_BLOCK_SIZE=0x40000
CONFIG_SYS_NAND_PAGE_SIZE=4096
CONFIG_SYS_NAND_OOBSIZE=256
CONFIG_SYS_NAND_U_BOOT_LOCATIONS=y
CONFIG_SYS_NAND_U_BOOT_OFFS=0x800000
CONFIG_SYS_NAND_U_BOOT_OFFS_REDUND=0x800000

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
FILESEXTRAPATHS_prepend := "${THISDIR}/files:"
SRC_URI_append_cubietruck = " file://cubietruck_nand.cfg"
SPL_ECC_BINARY = "sunxi-spl-with-ecc.bin"
SPL_ECC_BINARYNAME = "${@os.path.basename(d.getVar('SPL_ECC_BINARY'))}"
SPL_ECC_IMAGE = "${SPL_ECC_BINARYNAME}-${MACHINE}-${PV}-${PR}"
SPL_ECC_SYMLINK = "${SPL_ECC_BINARYNAME}-${MACHINE}"
do_install_append_cubietruck() {
install -m 644 ${B}/spl/${SPL_ECC_BINARY} ${D}/boot/${SPL_ECC_IMAGE}-${type}-${PV}-${PR}
ln -sf ${SPL_ECC_IMAGE}-${type}-${PV}-${PR} ${D}/boot/${SPL_ECC_BINARYNAME}-${type}
ln -sf ${SPL_ECC_IMAGE}-${type}-${PV}-${PR} ${D}/boot/${SPL_ECC_BINARYNAME}
}
do_deploy_append_cubietruck() {
install -m 644 ${B}/spl/${SPL_ECC_BINARY} ${DEPLOYDIR}/${SPL_ECC_IMAGE}
rm -f ${DEPLOYDIR}/${SPL_ECC_BINARYNAME} ${DEPLOYDIR}/${SPL_ECC_SYMLINK}
ln -sf ${SPL_ECC_IMAGE} ${DEPLOYDIR}/${SPL_ECC_BINARYNAME}
ln -sf ${SPL_ECC_IMAGE} ${DEPLOYDIR}/${SPL_ECC_SYMLINK}
}

View File

@@ -0,0 +1,31 @@
DESCRIPTION = "Small image capable of booting a cubietruck with support for the \
Minimal MTD Utilities, which let the user interact with the MTD subsystem in \
the kernel to perform operations on flash devices."
PR = "r1"
COMPATIBLE_MACHINE = "cubietruck"
IMAGE_INSTALL = "packagegroup-core-boot \
${CORE_IMAGE_EXTRA_INSTALL} \
mtd-utils \
dosfstools \
e2fsprogs-e2fsck \
e2fsprogs-mke2fs \
e2fsprogs-tune2fs \
rsync \
util-linux-mkfs \
sunxi-tools \
mtd-utils-ubifs \
mtd-utils-misc \
"
IMAGE_LINGUAS = " "
LICENSE = "MIT"
inherit core-image
IMAGE_ROOTFS_SIZE="1384000"
IMAGE_FEATURES += "ssh-server-openssh"

View File

@@ -0,0 +1,17 @@
COMPATIBLE_MACHINE = "(tinker-board-s|cubietruck)"
inherit image
WKS_FILE_tinker-board-s = "tinker-board-s.wks.in"
WKS_FILE_cubietruck = "cubietruck.wks.in"
IMAGE_FSTYPES = "wic.bz2 wic.bmap"
IMAGE_BASENAME = "klipper-image"
do_image_wic[depends] += "\
virtual/bootloader:do_deploy \
virtual/kernel:do_deploy \
klipper-linux-rootfs:do_image_complete \
"

View File

@@ -7,10 +7,8 @@ inherit packagegroup
RDEPENDS_${PN} = "\
e2fsprogs-e2fsck \
e2fsprogs-resize2fs \
kernel-image-zimage \
kernel-devicetree \
klipper \
octoprint \
lighttpd \
avahi-daemon \
avahi-autoipd \
@@ -25,4 +23,13 @@ RDEPENDS_${PN} = "\
tzdata-europe \
tzdata-pacific \
ntp \
ntpdate \
"
RDEPENDS_${PN}_append_tinker_board_s = "\
kernel-image-zimage \
"
RDEPENDS_${PN}_append_cubietruck = "\
kernel-image-uimage \
"

View File

@@ -0,0 +1,2 @@
# otherwise util-linux-native fails to find magic.h on Centos 8
DEPENDS += "file"

View File

@@ -0,0 +1 @@
CMAKE_EXTRACONF += "-DCMAKE_USE_SYSTEM_LIBRARIES=0"

View File

@@ -0,0 +1,2 @@
CONFIG_USB_SERIAL=y
CONFIG_USB_SERIAL_CH341=y

View File

@@ -0,0 +1 @@
USB_SERIAL_FTDI_SIO=y

View File

@@ -0,0 +1,5 @@
FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
SRC_URI += "file://usbserial_ch341.cfg \
file://usbserial_ftdi.cfg \
"

View File

@@ -1 +0,0 @@
CONFIG_USB_ANNOUNCE_NEW_DEVICES=y

View File

@@ -0,0 +1,2 @@
CONFIG_USB_SERIAL_GENERIC=y
CONFIG_USB_SERIAL_FTDI_SIO=y

View File

@@ -2,5 +2,5 @@ FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
SRC_URI += "\
file://usbserial_ch341.cfg \
file://usbannounce.cfg \
file://usbserial_ftdi.cfg \
"

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python3
#
# Copyright (c) 2007 Red Hat, Inc.
# Copyright (c) 2011 Intel, Inc.
#
# SPDX-License-Identifier: GPL-2.0-only
#
class WicError(Exception):
pass

View File

@@ -0,0 +1,3 @@
# This file is included into 3 canned wks files from this directory
part /boot --source bootimg-pcbios --ondisk sda --label boot --active --align 1024
part / --source rootfs --use-uuid --fstype=ext4 --label platform --align 1024

View File

@@ -0,0 +1,27 @@
# This is an example configuration file for syslinux.
TIMEOUT 50
ALLOWOPTIONS 1
SERIAL 0 115200
PROMPT 0
UI vesamenu.c32
menu title Select boot options
menu tabmsg Press [Tab] to edit, [Return] to select
DEFAULT Graphics console boot
LABEL Graphics console boot
KERNEL /vmlinuz
APPEND label=boot rootwait
LABEL Serial console boot
KERNEL /vmlinuz
APPEND label=boot rootwait console=ttyS0,115200
LABEL Graphics console install
KERNEL /vmlinuz
APPEND label=install rootwait
LABEL Serial console install
KERNEL /vmlinuz
APPEND label=install rootwait console=ttyS0,115200

View File

@@ -0,0 +1,8 @@
# short-description: Create a 'pcbios' direct disk image with custom bootloader config
# long-description: Creates a partitioned legacy BIOS disk image that the user
# can directly dd to boot media. The bootloader configuration source is a user file.
include common.wks.inc
bootloader --configfile="directdisk-bootloader-config.cfg"

View File

@@ -0,0 +1,10 @@
# short-description: Create a 'pcbios' direct disk image
# long-description: Creates a partitioned legacy BIOS disk image that the user
# can directly dd to boot media.
part /boot --source bootimg-pcbios --ondisk sda --label boot --active --align 1024
part / --source rootfs --ondisk sda --fstype=ext4 --label platform --align 1024 --use-uuid
bootloader --ptable gpt --timeout=0 --append="rootwait rootfstype=ext4 video=vesafb vga=0x318 console=tty0 console=ttyS0,115200n8"

View File

@@ -0,0 +1,23 @@
# short-description: Create multi rootfs image using rootfs plugin
# long-description: Creates a partitioned disk image with two rootfs partitions
# using rootfs plugin.
#
# Partitions can use either
# - indirect rootfs references to image recipe(s):
# wic create directdisk-multi-indirect-recipes -e core-image-minimal \
# --rootfs-dir rootfs1=core-image-minimal
# --rootfs-dir rootfs2=core-image-minimal-dev
#
# - or paths to rootfs directories:
# wic create directdisk-multi-rootfs \
# --rootfs-dir rootfs1=tmp/work/qemux86_64-poky-linux/core-image-minimal/1.0-r0/rootfs/
# --rootfs-dir rootfs2=tmp/work/qemux86_64-poky-linux/core-image-minimal-dev/1.0-r0/rootfs/
#
# - or any combinations of -r and --rootfs command line options
part /boot --source bootimg-pcbios --ondisk sda --label boot --active --align 1024
part / --source rootfs --rootfs-dir=rootfs1 --ondisk sda --fstype=ext4 --label platform --align 1024
part /rescue --source rootfs --rootfs-dir=rootfs2 --ondisk sda --fstype=ext4 --label secondary --align 1024
bootloader --timeout=0 --append="rootwait rootfstype=ext4 video=vesafb vga=0x318 console=tty0 console=ttyS0,115200n8"

View File

@@ -0,0 +1,8 @@
# short-description: Create a 'pcbios' direct disk image
# long-description: Creates a partitioned legacy BIOS disk image that the user
# can directly dd to boot media.
include common.wks.inc
bootloader --timeout=0 --append="rootwait rootfstype=ext4 video=vesafb vga=0x318 console=tty0 console=ttyS0,115200n8"

View File

@@ -0,0 +1,3 @@
bootloader --ptable gpt
part /boot --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/boot --fstype=vfat --label boot --active --align 1024 --use-uuid --overhead-factor 1.0
part / --source rootfs --fstype=ext4 --label root --align 1024 --exclude-path boot/

View File

@@ -0,0 +1,11 @@
# short-description: Create an EFI disk image
# long-description: Creates a partitioned EFI disk image that the user
# can directly dd to boot media.
part /boot --source bootimg-efi --sourceparams="loader=grub-efi" --ondisk sda --label msdos --active --align 1024
part / --source rootfs --ondisk sda --fstype=ext4 --label platform --align 1024 --use-uuid
part swap --ondisk sda --size 44 --label swap1 --fstype=swap
bootloader --ptable gpt --timeout=5 --append="rootfstype=ext4 console=ttyS0,115200 console=tty0"

View File

@@ -0,0 +1,7 @@
# short-description: Create a hybrid ISO image
# long-description: Creates an EFI and legacy bootable hybrid ISO image
# which can be used on optical media as well as USB media.
part /boot --source isoimage-isohybrid --sourceparams="loader=grub-efi,image_name=HYBRID_ISO_IMG" --ondisk cd --label HYBRIDISO
bootloader --timeout=15 --append=""

View File

@@ -0,0 +1,3 @@
# short-description: Create qcow2 image for RISC-V QEMU machines
part / --source rootfs --fstype=ext4 --label root --align 4096 --size 5G

View File

@@ -0,0 +1,8 @@
# short-description: Create a qemu machine 'pcbios' direct disk image
# long-description: Creates a partitioned legacy BIOS disk image that the user
# can directly use to boot a qemu machine.
include common.wks.inc
bootloader --timeout=0 --append="rw oprofile.timer=1 rootfstype=ext4 "

View File

@@ -0,0 +1,6 @@
# short-description: Create SD card image with a boot partition
# long-description: Creates a partitioned SD card image. Boot files
# are located in the first vfat partition.
part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4 --size 16
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4

View File

@@ -0,0 +1,11 @@
# short-description: Create an EFI disk image with systemd-boot
# long-description: Creates a partitioned EFI disk image that the user
# can directly dd to boot media. The selected bootloader is systemd-boot.
part /boot --source bootimg-efi --sourceparams="loader=systemd-boot" --ondisk sda --label msdos --active --align 1024 --use-uuid
part / --source rootfs --ondisk sda --fstype=ext4 --label platform --align 1024 --use-uuid
part swap --ondisk sda --size 44 --label swap1 --fstype=swap --use-uuid
bootloader --ptable gpt --timeout=5 --append="rootwait rootfstype=ext4 console=ttyS0,115200 console=tty0"

View File

@@ -0,0 +1,628 @@
#
# Copyright (c) 2013, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This module implements the image creation engine used by 'wic' to
# create images. The engine parses through the OpenEmbedded kickstart
# (wks) file specified and generates images that can then be directly
# written onto media.
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
#
import logging
import os
import tempfile
import json
import subprocess
import re
from collections import namedtuple, OrderedDict
from distutils.spawn import find_executable
from wic import WicError
from wic.filemap import sparse_copy
from wic.pluginbase import PluginMgr
from wic.misc import get_bitbake_var, exec_cmd
logger = logging.getLogger('wic')
def verify_build_env():
"""
Verify that the build environment is sane.
Returns True if it is, false otherwise
"""
if not os.environ.get("BUILDDIR"):
raise WicError("BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)")
return True
CANNED_IMAGE_DIR = "lib/wic/canned-wks" # relative to scripts
SCRIPTS_CANNED_IMAGE_DIR = "scripts/" + CANNED_IMAGE_DIR
WIC_DIR = "wic"
def build_canned_image_list(path):
layers_path = get_bitbake_var("BBLAYERS")
canned_wks_layer_dirs = []
if layers_path is not None:
for layer_path in layers_path.split():
for wks_path in (WIC_DIR, SCRIPTS_CANNED_IMAGE_DIR):
cpath = os.path.join(layer_path, wks_path)
if os.path.isdir(cpath):
canned_wks_layer_dirs.append(cpath)
cpath = os.path.join(path, CANNED_IMAGE_DIR)
canned_wks_layer_dirs.append(cpath)
return canned_wks_layer_dirs
def find_canned_image(scripts_path, wks_file):
"""
Find a .wks file with the given name in the canned files dir.
Return False if not found
"""
layers_canned_wks_dir = build_canned_image_list(scripts_path)
for canned_wks_dir in layers_canned_wks_dir:
for root, dirs, files in os.walk(canned_wks_dir):
for fname in files:
if fname.endswith("~") or fname.endswith("#"):
continue
if ((fname.endswith(".wks") and wks_file + ".wks" == fname) or \
(fname.endswith(".wks.in") and wks_file + ".wks.in" == fname)):
fullpath = os.path.join(canned_wks_dir, fname)
return fullpath
return None
def list_canned_images(scripts_path):
"""
List the .wks files in the canned image dir, minus the extension.
"""
layers_canned_wks_dir = build_canned_image_list(scripts_path)
for canned_wks_dir in layers_canned_wks_dir:
for root, dirs, files in os.walk(canned_wks_dir):
for fname in files:
if fname.endswith("~") or fname.endswith("#"):
continue
if fname.endswith(".wks") or fname.endswith(".wks.in"):
fullpath = os.path.join(canned_wks_dir, fname)
with open(fullpath) as wks:
for line in wks:
desc = ""
idx = line.find("short-description:")
if idx != -1:
desc = line[idx + len("short-description:"):].strip()
break
basename = fname.split('.')[0]
print(" %s\t\t%s" % (basename.ljust(30), desc))
def list_canned_image_help(scripts_path, fullpath):
"""
List the help and params in the specified canned image.
"""
found = False
with open(fullpath) as wks:
for line in wks:
if not found:
idx = line.find("long-description:")
if idx != -1:
print()
print(line[idx + len("long-description:"):].strip())
found = True
continue
if not line.strip():
break
idx = line.find("#")
if idx != -1:
print(line[idx + len("#:"):].rstrip())
else:
break
def list_source_plugins():
"""
List the available source plugins i.e. plugins available for --source.
"""
plugins = PluginMgr.get_plugins('source')
for plugin in plugins:
print(" %s" % plugin)
def wic_create(wks_file, rootfs_dir, bootimg_dir, kernel_dir,
native_sysroot, options):
"""
Create image
wks_file - user-defined OE kickstart file
rootfs_dir - absolute path to the build's /rootfs dir
bootimg_dir - absolute path to the build's boot artifacts directory
kernel_dir - absolute path to the build's kernel directory
native_sysroot - absolute path to the build's native sysroots dir
image_output_dir - dirname to create for image
options - wic command line options (debug, bmap, etc)
Normally, the values for the build artifacts values are determined
by 'wic -e' from the output of the 'bitbake -e' command given an
image name e.g. 'core-image-minimal' and a given machine set in
local.conf. If that's the case, the variables get the following
values from the output of 'bitbake -e':
rootfs_dir: IMAGE_ROOTFS
kernel_dir: DEPLOY_DIR_IMAGE
native_sysroot: STAGING_DIR_NATIVE
In the above case, bootimg_dir remains unset and the
plugin-specific image creation code is responsible for finding the
bootimg artifacts.
In the case where the values are passed in explicitly i.e 'wic -e'
is not used but rather the individual 'wic' options are used to
explicitly specify these values.
"""
try:
oe_builddir = os.environ["BUILDDIR"]
except KeyError:
raise WicError("BUILDDIR not found, exiting. (Did you forget to source oe-init-build-env?)")
if not os.path.exists(options.outdir):
os.makedirs(options.outdir)
pname = options.imager
plugin_class = PluginMgr.get_plugins('imager').get(pname)
if not plugin_class:
raise WicError('Unknown plugin: %s' % pname)
plugin = plugin_class(wks_file, rootfs_dir, bootimg_dir, kernel_dir,
native_sysroot, oe_builddir, options)
plugin.do_create()
logger.info("The image(s) were created using OE kickstart file:\n %s", wks_file)
def wic_list(args, scripts_path):
"""
Print the list of images or source plugins.
"""
if args.list_type is None:
return False
if args.list_type == "images":
list_canned_images(scripts_path)
return True
elif args.list_type == "source-plugins":
list_source_plugins()
return True
elif len(args.help_for) == 1 and args.help_for[0] == 'help':
wks_file = args.list_type
fullpath = find_canned_image(scripts_path, wks_file)
if not fullpath:
raise WicError("No image named %s found, exiting. "
"(Use 'wic list images' to list available images, "
"or specify a fully-qualified OE kickstart (.wks) "
"filename)" % wks_file)
list_canned_image_help(scripts_path, fullpath)
return True
return False
class Disk:
def __init__(self, imagepath, native_sysroot, fstypes=('fat', 'ext')):
self.imagepath = imagepath
self.native_sysroot = native_sysroot
self.fstypes = fstypes
self._partitions = None
self._partimages = {}
self._lsector_size = None
self._psector_size = None
self._ptable_format = None
# find parted
# read paths from $PATH environment variable
# if it fails, use hardcoded paths
pathlist = "/bin:/usr/bin:/usr/sbin:/sbin/"
try:
self.paths = os.environ['PATH'] + ":" + pathlist
except KeyError:
self.paths = pathlist
if native_sysroot:
for path in pathlist.split(':'):
self.paths = "%s%s:%s" % (native_sysroot, path, self.paths)
self.parted = find_executable("parted", self.paths)
if not self.parted:
raise WicError("Can't find executable parted")
self.partitions = self.get_partitions()
def __del__(self):
for path in self._partimages.values():
os.unlink(path)
def get_partitions(self):
if self._partitions is None:
self._partitions = OrderedDict()
out = exec_cmd("%s -sm %s unit B print" % (self.parted, self.imagepath))
parttype = namedtuple("Part", "pnum start end size fstype")
splitted = out.splitlines()
# skip over possible errors in exec_cmd output
try:
idx =splitted.index("BYT;")
except ValueError:
raise WicError("Error getting partition information from %s" % (self.parted))
lsector_size, psector_size, self._ptable_format = splitted[idx + 1].split(":")[3:6]
self._lsector_size = int(lsector_size)
self._psector_size = int(psector_size)
for line in splitted[idx + 2:]:
pnum, start, end, size, fstype = line.split(':')[:5]
partition = parttype(int(pnum), int(start[:-1]), int(end[:-1]),
int(size[:-1]), fstype)
self._partitions[pnum] = partition
return self._partitions
def __getattr__(self, name):
"""Get path to the executable in a lazy way."""
if name in ("mdir", "mcopy", "mdel", "mdeltree", "sfdisk", "e2fsck",
"resize2fs", "mkswap", "mkdosfs", "debugfs","blkid"):
aname = "_%s" % name
if aname not in self.__dict__:
setattr(self, aname, find_executable(name, self.paths))
if aname not in self.__dict__ or self.__dict__[aname] is None:
raise WicError("Can't find executable '{}'".format(name))
return self.__dict__[aname]
return self.__dict__[name]
def _get_part_image(self, pnum):
if pnum not in self.partitions:
raise WicError("Partition %s is not in the image" % pnum)
part = self.partitions[pnum]
# check if fstype is supported
for fstype in self.fstypes:
if part.fstype.startswith(fstype):
break
else:
raise WicError("Not supported fstype: {}".format(part.fstype))
if pnum not in self._partimages:
tmpf = tempfile.NamedTemporaryFile(prefix="wic-part")
dst_fname = tmpf.name
tmpf.close()
sparse_copy(self.imagepath, dst_fname, skip=part.start, length=part.size)
self._partimages[pnum] = dst_fname
return self._partimages[pnum]
def _put_part_image(self, pnum):
"""Put partition image into partitioned image."""
sparse_copy(self._partimages[pnum], self.imagepath,
seek=self.partitions[pnum].start)
def dir(self, pnum, path):
if pnum not in self.partitions:
raise WicError("Partition %s is not in the image" % pnum)
if self.partitions[pnum].fstype.startswith('ext'):
return exec_cmd("{} {} -R 'ls -l {}'".format(self.debugfs,
self._get_part_image(pnum),
path), as_shell=True)
else: # fat
return exec_cmd("{} -i {} ::{}".format(self.mdir,
self._get_part_image(pnum),
path))
def copy(self, src, dest):
"""Copy partition image into wic image."""
pnum = dest.part if isinstance(src, str) else src.part
if self.partitions[pnum].fstype.startswith('ext'):
if isinstance(src, str):
cmd = "printf 'cd {}\nwrite {} {}\n' | {} -w {}".\
format(os.path.dirname(dest.path), src, os.path.basename(src),
self.debugfs, self._get_part_image(pnum))
else: # copy from wic
# run both dump and rdump to support both files and directory
cmd = "printf 'cd {}\ndump /{} {}\nrdump /{} {}\n' | {} {}".\
format(os.path.dirname(src.path), src.path,
dest, src.path, dest, self.debugfs,
self._get_part_image(pnum))
else: # fat
if isinstance(src, str):
cmd = "{} -i {} -snop {} ::{}".format(self.mcopy,
self._get_part_image(pnum),
src, dest.path)
else:
cmd = "{} -i {} -snop ::{} {}".format(self.mcopy,
self._get_part_image(pnum),
src.path, dest)
exec_cmd(cmd, as_shell=True)
self._put_part_image(pnum)
def remove_ext(self, pnum, path, recursive):
"""
Remove files/dirs and their contents from the partition.
This only applies to ext* partition.
"""
abs_path = re.sub('\/\/+', '/', path)
cmd = "{} {} -wR 'rm \"{}\"'".format(self.debugfs,
self._get_part_image(pnum),
abs_path)
out = exec_cmd(cmd , as_shell=True)
for line in out.splitlines():
if line.startswith("rm:"):
if "file is a directory" in line:
if recursive:
# loop through content and delete them one by one if
# flaged with -r
subdirs = iter(self.dir(pnum, abs_path).splitlines())
next(subdirs)
for subdir in subdirs:
dir = subdir.split(':')[1].split(" ", 1)[1]
if not dir == "." and not dir == "..":
self.remove_ext(pnum, "%s/%s" % (abs_path, dir), recursive)
rmdir_out = exec_cmd("{} {} -wR 'rmdir \"{}\"'".format(self.debugfs,
self._get_part_image(pnum),
abs_path.rstrip('/'))
, as_shell=True)
for rmdir_line in rmdir_out.splitlines():
if "directory not empty" in rmdir_line:
raise WicError("Could not complete operation: \n%s \n"
"use -r to remove non-empty directory" % rmdir_line)
if rmdir_line.startswith("rmdir:"):
raise WicError("Could not complete operation: \n%s "
"\n%s" % (str(line), rmdir_line))
else:
raise WicError("Could not complete operation: \n%s "
"\nUnable to remove %s" % (str(line), abs_path))
def remove(self, pnum, path, recursive):
"""Remove files/dirs from the partition."""
partimg = self._get_part_image(pnum)
if self.partitions[pnum].fstype.startswith('ext'):
self.remove_ext(pnum, path, recursive)
else: # fat
cmd = "{} -i {} ::{}".format(self.mdel, partimg, path)
try:
exec_cmd(cmd)
except WicError as err:
if "not found" in str(err) or "non empty" in str(err):
# mdel outputs 'File ... not found' or 'directory .. non empty"
# try to use mdeltree as path could be a directory
cmd = "{} -i {} ::{}".format(self.mdeltree,
partimg, path)
exec_cmd(cmd)
else:
raise err
self._put_part_image(pnum)
def write(self, target, expand):
"""Write disk image to the media or file."""
def write_sfdisk_script(outf, parts):
for key, val in parts['partitiontable'].items():
if key in ("partitions", "device", "firstlba", "lastlba"):
continue
if key == "id":
key = "label-id"
outf.write("{}: {}\n".format(key, val))
outf.write("\n")
for part in parts['partitiontable']['partitions']:
line = ''
for name in ('attrs', 'name', 'size', 'type', 'uuid'):
if name == 'size' and part['type'] == 'f':
# don't write size for extended partition
continue
val = part.get(name)
if val:
line += '{}={}, '.format(name, val)
if line:
line = line[:-2] # strip ', '
if part.get('bootable'):
line += ' ,bootable'
outf.write("{}\n".format(line))
outf.flush()
def read_ptable(path):
out = exec_cmd("{} -J {}".format(self.sfdisk, path))
return json.loads(out)
def write_ptable(parts, target):
with tempfile.NamedTemporaryFile(prefix="wic-sfdisk-", mode='w') as outf:
write_sfdisk_script(outf, parts)
cmd = "{} --no-reread {} < {} ".format(self.sfdisk, target, outf.name)
exec_cmd(cmd, as_shell=True)
if expand is None:
sparse_copy(self.imagepath, target)
else:
# copy first sectors that may contain bootloader
sparse_copy(self.imagepath, target, length=2048 * self._lsector_size)
# copy source partition table to the target
parts = read_ptable(self.imagepath)
write_ptable(parts, target)
# get size of unpartitioned space
free = None
for line in exec_cmd("{} -F {}".format(self.sfdisk, target)).splitlines():
if line.startswith("Unpartitioned space ") and line.endswith("sectors"):
free = int(line.split()[-2])
# Align free space to a 2048 sector boundary. YOCTO #12840.
free = free - (free % 2048)
if free is None:
raise WicError("Can't get size of unpartitioned space")
# calculate expanded partitions sizes
sizes = {}
num_auto_resize = 0
for num, part in enumerate(parts['partitiontable']['partitions'], 1):
if num in expand:
if expand[num] != 0: # don't resize partition if size is set to 0
sectors = expand[num] // self._lsector_size
free -= sectors - part['size']
part['size'] = sectors
sizes[num] = sectors
elif part['type'] != 'f':
sizes[num] = -1
num_auto_resize += 1
for num, part in enumerate(parts['partitiontable']['partitions'], 1):
if sizes.get(num) == -1:
part['size'] += free // num_auto_resize
# write resized partition table to the target
write_ptable(parts, target)
# read resized partition table
parts = read_ptable(target)
# copy partitions content
for num, part in enumerate(parts['partitiontable']['partitions'], 1):
pnum = str(num)
fstype = self.partitions[pnum].fstype
# copy unchanged partition
if part['size'] == self.partitions[pnum].size // self._lsector_size:
logger.info("copying unchanged partition {}".format(pnum))
sparse_copy(self._get_part_image(pnum), target, seek=part['start'] * self._lsector_size)
continue
# resize or re-create partitions
if fstype.startswith('ext') or fstype.startswith('fat') or \
fstype.startswith('linux-swap'):
partfname = None
with tempfile.NamedTemporaryFile(prefix="wic-part{}-".format(pnum)) as partf:
partfname = partf.name
if fstype.startswith('ext'):
logger.info("resizing ext partition {}".format(pnum))
partimg = self._get_part_image(pnum)
sparse_copy(partimg, partfname)
exec_cmd("{} -pf {}".format(self.e2fsck, partfname))
exec_cmd("{} {} {}s".format(\
self.resize2fs, partfname, part['size']))
elif fstype.startswith('fat'):
logger.info("copying content of the fat partition {}".format(pnum))
with tempfile.TemporaryDirectory(prefix='wic-fatdir-') as tmpdir:
# copy content to the temporary directory
cmd = "{} -snompi {} :: {}".format(self.mcopy,
self._get_part_image(pnum),
tmpdir)
exec_cmd(cmd)
# create new msdos partition
label = part.get("name")
label_str = "-n {}".format(label) if label else ''
cmd = "{} {} -C {} {}".format(self.mkdosfs, label_str, partfname,
part['size'])
exec_cmd(cmd)
# copy content from the temporary directory to the new partition
cmd = "{} -snompi {} {}/* ::".format(self.mcopy, partfname, tmpdir)
exec_cmd(cmd, as_shell=True)
elif fstype.startswith('linux-swap'):
logger.info("creating swap partition {}".format(pnum))
label = part.get("name")
label_str = "-L {}".format(label) if label else ''
out = exec_cmd("{} --probe {}".format(self.blkid, self._get_part_image(pnum)))
uuid = out[out.index("UUID=\"")+6:out.index("UUID=\"")+42]
uuid_str = "-U {}".format(uuid) if uuid else ''
with open(partfname, 'w') as sparse:
os.ftruncate(sparse.fileno(), part['size'] * self._lsector_size)
exec_cmd("{} {} {} {}".format(self.mkswap, label_str, uuid_str, partfname))
sparse_copy(partfname, target, seek=part['start'] * self._lsector_size)
os.unlink(partfname)
elif part['type'] != 'f':
logger.warning("skipping partition {}: unsupported fstype {}".format(pnum, fstype))
def wic_ls(args, native_sysroot):
"""List contents of partitioned image or vfat partition."""
disk = Disk(args.path.image, native_sysroot)
if not args.path.part:
if disk.partitions:
print('Num Start End Size Fstype')
for part in disk.partitions.values():
print("{:2d} {:12d} {:12d} {:12d} {}".format(\
part.pnum, part.start, part.end,
part.size, part.fstype))
else:
path = args.path.path or '/'
print(disk.dir(args.path.part, path))
def wic_cp(args, native_sysroot):
"""
Copy file or directory to/from the vfat/ext partition of
partitioned image.
"""
if isinstance(args.dest, str):
disk = Disk(args.src.image, native_sysroot)
else:
disk = Disk(args.dest.image, native_sysroot)
disk.copy(args.src, args.dest)
def wic_rm(args, native_sysroot):
"""
Remove files or directories from the vfat partition of
partitioned image.
"""
disk = Disk(args.path.image, native_sysroot)
disk.remove(args.path.part, args.path.path, args.recursive_delete)
def wic_write(args, native_sysroot):
"""
Write image to a target device.
"""
disk = Disk(args.image, native_sysroot, ('fat', 'ext', 'linux-swap'))
disk.write(args.target, args.expand)
def find_canned(scripts_path, file_name):
"""
Find a file either by its path or by name in the canned files dir.
Return None if not found
"""
if os.path.exists(file_name):
return file_name
layers_canned_wks_dir = build_canned_image_list(scripts_path)
for canned_wks_dir in layers_canned_wks_dir:
for root, dirs, files in os.walk(canned_wks_dir):
for fname in files:
if fname == file_name:
fullpath = os.path.join(canned_wks_dir, fname)
return fullpath
def get_custom_config(boot_file):
"""
Get the custom configuration to be used for the bootloader.
Return None if the file can't be found.
"""
# Get the scripts path of poky
scripts_path = os.path.abspath("%s/../.." % os.path.dirname(__file__))
cfg_file = find_canned(scripts_path, boot_file)
if cfg_file:
with open(cfg_file, "r") as f:
config = f.read()
return config

View File

@@ -0,0 +1,576 @@
#
# Copyright (c) 2012 Intel, Inc.
#
# SPDX-License-Identifier: GPL-2.0-only
#
"""
This module implements python implements a way to get file block. Two methods
are supported - the FIEMAP ioctl and the 'SEEK_HOLE / SEEK_DATA' features of
the file seek syscall. The former is implemented by the 'FilemapFiemap' class,
the latter is implemented by the 'FilemapSeek' class. Both classes provide the
same API. The 'filemap' function automatically selects which class can be used
and returns an instance of the class.
"""
# Disable the following pylint recommendations:
# * Too many instance attributes (R0902)
# pylint: disable=R0902
import errno
import os
import struct
import array
import fcntl
import tempfile
import logging
def get_block_size(file_obj):
"""
Returns block size for file object 'file_obj'. Errors are indicated by the
'IOError' exception.
"""
# Get the block size of the host file-system for the image file by calling
# the FIGETBSZ ioctl (number 2).
try:
binary_data = fcntl.ioctl(file_obj, 2, struct.pack('I', 0))
bsize = struct.unpack('I', binary_data)[0]
except OSError:
bsize = None
# If ioctl causes OSError or give bsize to zero failback to os.fstat
if not bsize:
import os
stat = os.fstat(file_obj.fileno())
if hasattr(stat, 'st_blksize'):
bsize = stat.st_blksize
else:
raise IOError("Unable to determine block size")
return bsize
class ErrorNotSupp(Exception):
"""
An exception of this type is raised when the 'FIEMAP' or 'SEEK_HOLE' feature
is not supported either by the kernel or the file-system.
"""
pass
class Error(Exception):
"""A class for all the other exceptions raised by this module."""
pass
class _FilemapBase(object):
"""
This is a base class for a couple of other classes in this module. This
class simply performs the common parts of the initialization process: opens
the image file, gets its size, etc. The 'log' parameter is the logger object
to use for printing messages.
"""
def __init__(self, image, log=None):
"""
Initialize a class instance. The 'image' argument is full path to the
file or file object to operate on.
"""
self._log = log
if self._log is None:
self._log = logging.getLogger(__name__)
self._f_image_needs_close = False
if hasattr(image, "fileno"):
self._f_image = image
self._image_path = image.name
else:
self._image_path = image
self._open_image_file()
try:
self.image_size = os.fstat(self._f_image.fileno()).st_size
except IOError as err:
raise Error("cannot get information about file '%s': %s"
% (self._f_image.name, err))
try:
self.block_size = get_block_size(self._f_image)
except IOError as err:
raise Error("cannot get block size for '%s': %s"
% (self._image_path, err))
self.blocks_cnt = self.image_size + self.block_size - 1
self.blocks_cnt //= self.block_size
try:
self._f_image.flush()
except IOError as err:
raise Error("cannot flush image file '%s': %s"
% (self._image_path, err))
try:
os.fsync(self._f_image.fileno()),
except OSError as err:
raise Error("cannot synchronize image file '%s': %s "
% (self._image_path, err.strerror))
self._log.debug("opened image \"%s\"" % self._image_path)
self._log.debug("block size %d, blocks count %d, image size %d"
% (self.block_size, self.blocks_cnt, self.image_size))
def __del__(self):
"""The class destructor which just closes the image file."""
if self._f_image_needs_close:
self._f_image.close()
def _open_image_file(self):
"""Open the image file."""
try:
self._f_image = open(self._image_path, 'rb')
except IOError as err:
raise Error("cannot open image file '%s': %s"
% (self._image_path, err))
self._f_image_needs_close = True
def block_is_mapped(self, block): # pylint: disable=W0613,R0201
"""
This method has has to be implemented by child classes. It returns
'True' if block number 'block' of the image file is mapped and 'False'
otherwise.
"""
raise Error("the method is not implemented")
def get_mapped_ranges(self, start, count): # pylint: disable=W0613,R0201
"""
This method has has to be implemented by child classes. This is a
generator which yields ranges of mapped blocks in the file. The ranges
are tuples of 2 elements: [first, last], where 'first' is the first
mapped block and 'last' is the last mapped block.
The ranges are yielded for the area of the file of size 'count' blocks,
starting from block 'start'.
"""
raise Error("the method is not implemented")
# The 'SEEK_HOLE' and 'SEEK_DATA' options of the file seek system call
_SEEK_DATA = 3
_SEEK_HOLE = 4
def _lseek(file_obj, offset, whence):
"""This is a helper function which invokes 'os.lseek' for file object
'file_obj' and with specified 'offset' and 'whence'. The 'whence'
argument is supposed to be either '_SEEK_DATA' or '_SEEK_HOLE'. When
there is no more data or hole starting from 'offset', this function
returns '-1'. Otherwise the data or hole position is returned."""
try:
return os.lseek(file_obj.fileno(), offset, whence)
except OSError as err:
# The 'lseek' system call returns the ENXIO if there is no data or
# hole starting from the specified offset.
if err.errno == errno.ENXIO:
return -1
elif err.errno == errno.EINVAL:
raise ErrorNotSupp("the kernel or file-system does not support "
"\"SEEK_HOLE\" and \"SEEK_DATA\"")
else:
raise
class FilemapSeek(_FilemapBase):
"""
This class uses the 'SEEK_HOLE' and 'SEEK_DATA' to find file block mapping.
Unfortunately, the current implementation requires the caller to have write
access to the image file.
"""
def __init__(self, image, log=None):
"""Refer the '_FilemapBase' class for the documentation."""
# Call the base class constructor first
_FilemapBase.__init__(self, image, log)
self._log.debug("FilemapSeek: initializing")
self._probe_seek_hole()
def _probe_seek_hole(self):
"""
Check whether the system implements 'SEEK_HOLE' and 'SEEK_DATA'.
Unfortunately, there seems to be no clean way for detecting this,
because often the system just fakes them by just assuming that all
files are fully mapped, so 'SEEK_HOLE' always returns EOF and
'SEEK_DATA' always returns the requested offset.
I could not invent a better way of detecting the fake 'SEEK_HOLE'
implementation than just to create a temporary file in the same
directory where the image file resides. It would be nice to change this
to something better.
"""
directory = os.path.dirname(self._image_path)
try:
tmp_obj = tempfile.TemporaryFile("w+", dir=directory)
except IOError as err:
raise ErrorNotSupp("cannot create a temporary in \"%s\": %s" \
% (directory, err))
try:
os.ftruncate(tmp_obj.fileno(), self.block_size)
except OSError as err:
raise ErrorNotSupp("cannot truncate temporary file in \"%s\": %s"
% (directory, err))
offs = _lseek(tmp_obj, 0, _SEEK_HOLE)
if offs != 0:
# We are dealing with the stub 'SEEK_HOLE' implementation which
# always returns EOF.
self._log.debug("lseek(0, SEEK_HOLE) returned %d" % offs)
raise ErrorNotSupp("the file-system does not support "
"\"SEEK_HOLE\" and \"SEEK_DATA\" but only "
"provides a stub implementation")
tmp_obj.close()
def block_is_mapped(self, block):
"""Refer the '_FilemapBase' class for the documentation."""
offs = _lseek(self._f_image, block * self.block_size, _SEEK_DATA)
if offs == -1:
result = False
else:
result = (offs // self.block_size == block)
self._log.debug("FilemapSeek: block_is_mapped(%d) returns %s"
% (block, result))
return result
def _get_ranges(self, start, count, whence1, whence2):
"""
This function implements 'get_mapped_ranges()' depending
on what is passed in the 'whence1' and 'whence2' arguments.
"""
assert whence1 != whence2
end = start * self.block_size
limit = end + count * self.block_size
while True:
start = _lseek(self._f_image, end, whence1)
if start == -1 or start >= limit or start == self.image_size:
break
end = _lseek(self._f_image, start, whence2)
if end == -1 or end == self.image_size:
end = self.blocks_cnt * self.block_size
if end > limit:
end = limit
start_blk = start // self.block_size
end_blk = end // self.block_size - 1
self._log.debug("FilemapSeek: yielding range (%d, %d)"
% (start_blk, end_blk))
yield (start_blk, end_blk)
def get_mapped_ranges(self, start, count):
"""Refer the '_FilemapBase' class for the documentation."""
self._log.debug("FilemapSeek: get_mapped_ranges(%d, %d(%d))"
% (start, count, start + count - 1))
return self._get_ranges(start, count, _SEEK_DATA, _SEEK_HOLE)
# Below goes the FIEMAP ioctl implementation, which is not very readable
# because it deals with the rather complex FIEMAP ioctl. To understand the
# code, you need to know the FIEMAP interface, which is documented in the
# "Documentation/filesystems/fiemap.txt" file in the Linux kernel sources.
# Format string for 'struct fiemap'
_FIEMAP_FORMAT = "=QQLLLL"
# sizeof(struct fiemap)
_FIEMAP_SIZE = struct.calcsize(_FIEMAP_FORMAT)
# Format string for 'struct fiemap_extent'
_FIEMAP_EXTENT_FORMAT = "=QQQQQLLLL"
# sizeof(struct fiemap_extent)
_FIEMAP_EXTENT_SIZE = struct.calcsize(_FIEMAP_EXTENT_FORMAT)
# The FIEMAP ioctl number
_FIEMAP_IOCTL = 0xC020660B
# This FIEMAP ioctl flag which instructs the kernel to sync the file before
# reading the block map
_FIEMAP_FLAG_SYNC = 0x00000001
# Size of the buffer for 'struct fiemap_extent' elements which will be used
# when invoking the FIEMAP ioctl. The larger is the buffer, the less times the
# FIEMAP ioctl will be invoked.
_FIEMAP_BUFFER_SIZE = 256 * 1024
class FilemapFiemap(_FilemapBase):
"""
This class provides API to the FIEMAP ioctl. Namely, it allows to iterate
over all mapped blocks and over all holes.
This class synchronizes the image file every time it invokes the FIEMAP
ioctl in order to work-around early FIEMAP implementation kernel bugs.
"""
def __init__(self, image, log=None):
"""
Initialize a class instance. The 'image' argument is full the file
object to operate on.
"""
# Call the base class constructor first
_FilemapBase.__init__(self, image, log)
self._log.debug("FilemapFiemap: initializing")
self._buf_size = _FIEMAP_BUFFER_SIZE
# Calculate how many 'struct fiemap_extent' elements fit the buffer
self._buf_size -= _FIEMAP_SIZE
self._fiemap_extent_cnt = self._buf_size // _FIEMAP_EXTENT_SIZE
assert self._fiemap_extent_cnt > 0
self._buf_size = self._fiemap_extent_cnt * _FIEMAP_EXTENT_SIZE
self._buf_size += _FIEMAP_SIZE
# Allocate a mutable buffer for the FIEMAP ioctl
self._buf = array.array('B', [0] * self._buf_size)
# Check if the FIEMAP ioctl is supported
self.block_is_mapped(0)
def _invoke_fiemap(self, block, count):
"""
Invoke the FIEMAP ioctl for 'count' blocks of the file starting from
block number 'block'.
The full result of the operation is stored in 'self._buf' on exit.
Returns the unpacked 'struct fiemap' data structure in form of a python
list (just like 'struct.upack()').
"""
if self.blocks_cnt != 0 and (block < 0 or block >= self.blocks_cnt):
raise Error("bad block number %d, should be within [0, %d]"
% (block, self.blocks_cnt))
# Initialize the 'struct fiemap' part of the buffer. We use the
# '_FIEMAP_FLAG_SYNC' flag in order to make sure the file is
# synchronized. The reason for this is that early FIEMAP
# implementations had many bugs related to cached dirty data, and
# synchronizing the file is a necessary work-around.
struct.pack_into(_FIEMAP_FORMAT, self._buf, 0, block * self.block_size,
count * self.block_size, _FIEMAP_FLAG_SYNC, 0,
self._fiemap_extent_cnt, 0)
try:
fcntl.ioctl(self._f_image, _FIEMAP_IOCTL, self._buf, 1)
except IOError as err:
# Note, the FIEMAP ioctl is supported by the Linux kernel starting
# from version 2.6.28 (year 2008).
if err.errno == errno.EOPNOTSUPP:
errstr = "FilemapFiemap: the FIEMAP ioctl is not supported " \
"by the file-system"
self._log.debug(errstr)
raise ErrorNotSupp(errstr)
if err.errno == errno.ENOTTY:
errstr = "FilemapFiemap: the FIEMAP ioctl is not supported " \
"by the kernel"
self._log.debug(errstr)
raise ErrorNotSupp(errstr)
raise Error("the FIEMAP ioctl failed for '%s': %s"
% (self._image_path, err))
return struct.unpack(_FIEMAP_FORMAT, self._buf[:_FIEMAP_SIZE])
def block_is_mapped(self, block):
"""Refer the '_FilemapBase' class for the documentation."""
struct_fiemap = self._invoke_fiemap(block, 1)
# The 3rd element of 'struct_fiemap' is the 'fm_mapped_extents' field.
# If it contains zero, the block is not mapped, otherwise it is
# mapped.
result = bool(struct_fiemap[3])
self._log.debug("FilemapFiemap: block_is_mapped(%d) returns %s"
% (block, result))
return result
def _unpack_fiemap_extent(self, index):
"""
Unpack a 'struct fiemap_extent' structure object number 'index' from
the internal 'self._buf' buffer.
"""
offset = _FIEMAP_SIZE + _FIEMAP_EXTENT_SIZE * index
return struct.unpack(_FIEMAP_EXTENT_FORMAT,
self._buf[offset : offset + _FIEMAP_EXTENT_SIZE])
def _do_get_mapped_ranges(self, start, count):
"""
Implements most the functionality for the 'get_mapped_ranges()'
generator: invokes the FIEMAP ioctl, walks through the mapped extents
and yields mapped block ranges. However, the ranges may be consecutive
(e.g., (1, 100), (100, 200)) and 'get_mapped_ranges()' simply merges
them.
"""
block = start
while block < start + count:
struct_fiemap = self._invoke_fiemap(block, count)
mapped_extents = struct_fiemap[3]
if mapped_extents == 0:
# No more mapped blocks
return
extent = 0
while extent < mapped_extents:
fiemap_extent = self._unpack_fiemap_extent(extent)
# Start of the extent
extent_start = fiemap_extent[0]
# Starting block number of the extent
extent_block = extent_start // self.block_size
# Length of the extent
extent_len = fiemap_extent[2]
# Count of blocks in the extent
extent_count = extent_len // self.block_size
# Extent length and offset have to be block-aligned
assert extent_start % self.block_size == 0
assert extent_len % self.block_size == 0
if extent_block > start + count - 1:
return
first = max(extent_block, block)
last = min(extent_block + extent_count, start + count) - 1
yield (first, last)
extent += 1
block = extent_block + extent_count
def get_mapped_ranges(self, start, count):
"""Refer the '_FilemapBase' class for the documentation."""
self._log.debug("FilemapFiemap: get_mapped_ranges(%d, %d(%d))"
% (start, count, start + count - 1))
iterator = self._do_get_mapped_ranges(start, count)
first_prev, last_prev = next(iterator)
for first, last in iterator:
if last_prev == first - 1:
last_prev = last
else:
self._log.debug("FilemapFiemap: yielding range (%d, %d)"
% (first_prev, last_prev))
yield (first_prev, last_prev)
first_prev, last_prev = first, last
self._log.debug("FilemapFiemap: yielding range (%d, %d)"
% (first_prev, last_prev))
yield (first_prev, last_prev)
class FilemapNobmap(_FilemapBase):
"""
This class is used when both the 'SEEK_DATA/HOLE' and FIEMAP are not
supported by the filesystem or kernel.
"""
def __init__(self, image, log=None):
"""Refer the '_FilemapBase' class for the documentation."""
# Call the base class constructor first
_FilemapBase.__init__(self, image, log)
self._log.debug("FilemapNobmap: initializing")
def block_is_mapped(self, block):
"""Refer the '_FilemapBase' class for the documentation."""
return True
def get_mapped_ranges(self, start, count):
"""Refer the '_FilemapBase' class for the documentation."""
self._log.debug("FilemapNobmap: get_mapped_ranges(%d, %d(%d))"
% (start, count, start + count - 1))
yield (start, start + count -1)
def filemap(image, log=None):
"""
Create and return an instance of a Filemap class - 'FilemapFiemap' or
'FilemapSeek', depending on what the system we run on supports. If the
FIEMAP ioctl is supported, an instance of the 'FilemapFiemap' class is
returned. Otherwise, if 'SEEK_HOLE' is supported an instance of the
'FilemapSeek' class is returned. If none of these are supported, the
function generates an 'Error' type exception.
"""
try:
return FilemapFiemap(image, log)
except ErrorNotSupp:
try:
return FilemapSeek(image, log)
except ErrorNotSupp:
return FilemapNobmap(image, log)
def sparse_copy(src_fname, dst_fname, skip=0, seek=0,
length=0, api=None):
"""
Efficiently copy sparse file to or into another file.
src_fname: path to source file
dst_fname: path to destination file
skip: skip N bytes at thestart of src
seek: seek N bytes from the start of dst
length: read N bytes from src and write them to dst
api: FilemapFiemap or FilemapSeek object
"""
if not api:
api = filemap
fmap = api(src_fname)
try:
dst_file = open(dst_fname, 'r+b')
except IOError:
dst_file = open(dst_fname, 'wb')
if length:
dst_size = length + seek
else:
dst_size = os.path.getsize(src_fname) + seek - skip
dst_file.truncate(dst_size)
written = 0
for first, last in fmap.get_mapped_ranges(0, fmap.blocks_cnt):
start = first * fmap.block_size
end = (last + 1) * fmap.block_size
if skip >= end:
continue
if start < skip < end:
start = skip
fmap._f_image.seek(start, os.SEEK_SET)
written += start - skip - written
if length and written >= length:
dst_file.seek(seek + length, os.SEEK_SET)
dst_file.close()
return
dst_file.seek(seek + start - skip, os.SEEK_SET)
chunk_size = 1024 * 1024
to_read = end - start
read = 0
while read < to_read:
if read + chunk_size > to_read:
chunk_size = to_read - read
size = chunk_size
if length and written + size > length:
size = length - written
chunk = fmap._f_image.read(size)
dst_file.write(chunk)
read += size
written += size
if written == length:
dst_file.close()
return
dst_file.close()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,272 @@
#!/usr/bin/env python3
#
# Copyright (c) 2016 Intel, Inc.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This module provides parser for kickstart format
#
# AUTHORS
# Ed Bartosh <ed.bartosh> (at] linux.intel.com>
"""Kickstart parser module."""
import os
import shlex
import logging
import re
from argparse import ArgumentParser, ArgumentError, ArgumentTypeError
from wic.engine import find_canned
from wic.partition import Partition
from wic.misc import get_bitbake_var
logger = logging.getLogger('wic')
__expand_var_regexp__ = re.compile(r"\${[^{}@\n\t :]+}")
def expand_line(line):
while True:
m = __expand_var_regexp__.search(line)
if not m:
return line
key = m.group()[2:-1]
val = get_bitbake_var(key)
if val is None:
logger.warning("cannot expand variable %s" % key)
return line
line = line[:m.start()] + val + line[m.end():]
class KickStartError(Exception):
"""Custom exception."""
pass
class KickStartParser(ArgumentParser):
"""
This class overwrites error method to throw exception
instead of producing usage message(default argparse behavior).
"""
def error(self, message):
raise ArgumentError(None, message)
def sizetype(default, size_in_bytes=False):
def f(arg):
"""
Custom type for ArgumentParser
Converts size string in <num>[S|s|K|k|M|G] format into the integer value
"""
try:
suffix = default
size = int(arg)
except ValueError:
try:
suffix = arg[-1:]
size = int(arg[:-1])
except ValueError:
raise ArgumentTypeError("Invalid size: %r" % arg)
if size_in_bytes:
if suffix == 's' or suffix == 'S':
return size * 512
mult = 1024
else:
mult = 1
if suffix == "k" or suffix == "K":
return size * mult
if suffix == "M":
return size * mult * 1024
if suffix == "G":
return size * mult * 1024 * 1024
raise ArgumentTypeError("Invalid size: %r" % arg)
return f
def overheadtype(arg):
"""
Custom type for ArgumentParser
Converts overhead string to float and checks if it's bigger than 1.0
"""
try:
result = float(arg)
except ValueError:
raise ArgumentTypeError("Invalid value: %r" % arg)
if result < 1.0:
raise ArgumentTypeError("Overhead factor should be > 1.0" % arg)
return result
def cannedpathtype(arg):
"""
Custom type for ArgumentParser
Tries to find file in the list of canned wks paths
"""
scripts_path = os.path.abspath(os.path.dirname(__file__) + '../../..')
result = find_canned(scripts_path, arg)
if not result:
raise ArgumentTypeError("file not found: %s" % arg)
return result
def systemidtype(arg):
"""
Custom type for ArgumentParser
Checks if the argument sutisfies system id requirements,
i.e. if it's one byte long integer > 0
"""
error = "Invalid system type: %s. must be hex "\
"between 0x1 and 0xFF" % arg
try:
result = int(arg, 16)
except ValueError:
raise ArgumentTypeError(error)
if result <= 0 or result > 0xff:
raise ArgumentTypeError(error)
return arg
class KickStart():
"""Kickstart parser implementation."""
DEFAULT_EXTRA_SPACE = 10*1024
DEFAULT_OVERHEAD_FACTOR = 1.3
def __init__(self, confpath):
self.partitions = []
self.bootloader = None
self.lineno = 0
self.partnum = 0
parser = KickStartParser()
subparsers = parser.add_subparsers()
part = subparsers.add_parser('part')
part.add_argument('mountpoint', nargs='?')
part.add_argument('--active', action='store_true')
part.add_argument('--align', type=int)
part.add_argument('--offset', type=sizetype("K", True))
part.add_argument('--exclude-path', nargs='+')
part.add_argument('--include-path', nargs='+', action='append')
part.add_argument('--change-directory')
part.add_argument("--extra-space", type=sizetype("M"))
part.add_argument('--fsoptions', dest='fsopts')
part.add_argument('--fstype', default='vfat',
choices=('ext2', 'ext3', 'ext4', 'btrfs',
'squashfs', 'vfat', 'msdos', 'swap'))
part.add_argument('--mkfs-extraopts', default='')
part.add_argument('--label')
part.add_argument('--use-label', action='store_true')
part.add_argument('--no-table', action='store_true')
part.add_argument('--ondisk', '--ondrive', dest='disk', default='sda')
part.add_argument("--overhead-factor", type=overheadtype)
part.add_argument('--part-name')
part.add_argument('--part-type')
part.add_argument('--rootfs-dir')
part.add_argument('--type', default='primary',
choices = ('primary', 'logical'))
# --size and --fixed-size cannot be specified together; options
# ----extra-space and --overhead-factor should also raise a parser
# --error, but since nesting mutually exclusive groups does not work,
# ----extra-space/--overhead-factor are handled later
sizeexcl = part.add_mutually_exclusive_group()
sizeexcl.add_argument('--size', type=sizetype("M"), default=0)
sizeexcl.add_argument('--fixed-size', type=sizetype("M"), default=0)
part.add_argument('--source')
part.add_argument('--sourceparams')
part.add_argument('--system-id', type=systemidtype)
part.add_argument('--use-uuid', action='store_true')
part.add_argument('--uuid')
part.add_argument('--fsuuid')
bootloader = subparsers.add_parser('bootloader')
bootloader.add_argument('--append')
bootloader.add_argument('--configfile')
bootloader.add_argument('--ptable', choices=('msdos', 'gpt'),
default='msdos')
bootloader.add_argument('--timeout', type=int)
bootloader.add_argument('--source')
include = subparsers.add_parser('include')
include.add_argument('path', type=cannedpathtype)
self._parse(parser, confpath)
if not self.bootloader:
logger.warning('bootloader config not specified, using defaults\n')
self.bootloader = bootloader.parse_args([])
def _parse(self, parser, confpath):
"""
Parse file in .wks format using provided parser.
"""
with open(confpath) as conf:
lineno = 0
for line in conf:
line = line.strip()
lineno += 1
if line and line[0] != '#':
line = expand_line(line)
try:
line_args = shlex.split(line)
parsed = parser.parse_args(line_args)
except ArgumentError as err:
raise KickStartError('%s:%d: %s' % \
(confpath, lineno, err))
if line.startswith('part'):
# SquashFS does not support filesystem UUID
if parsed.fstype == 'squashfs':
if parsed.fsuuid:
err = "%s:%d: SquashFS does not support UUID" \
% (confpath, lineno)
raise KickStartError(err)
if parsed.label:
err = "%s:%d: SquashFS does not support LABEL" \
% (confpath, lineno)
raise KickStartError(err)
if parsed.use_label and not parsed.label:
err = "%s:%d: Must set the label with --label" \
% (confpath, lineno)
raise KickStartError(err)
# using ArgumentParser one cannot easily tell if option
# was passed as argument, if said option has a default
# value; --overhead-factor/--extra-space cannot be used
# with --fixed-size, so at least detect when these were
# passed with non-0 values ...
if parsed.fixed_size:
if parsed.overhead_factor or parsed.extra_space:
err = "%s:%d: arguments --overhead-factor and --extra-space not "\
"allowed with argument --fixed-size" \
% (confpath, lineno)
raise KickStartError(err)
else:
# ... and provide defaults if not using
# --fixed-size iff given option was not used
# (again, one cannot tell if option was passed but
# with value equal to 0)
if '--overhead-factor' not in line_args:
parsed.overhead_factor = self.DEFAULT_OVERHEAD_FACTOR
if '--extra-space' not in line_args:
parsed.extra_space = self.DEFAULT_EXTRA_SPACE
self.partnum += 1
self.partitions.append(Partition(parsed, self.partnum))
elif line.startswith('include'):
self._parse(parser, parsed.path)
elif line.startswith('bootloader'):
if not self.bootloader:
self.bootloader = parsed
# Concatenate the strings set in APPEND
append_var = get_bitbake_var("APPEND")
if append_var:
self.bootloader.append = ' '.join(filter(None, \
(self.bootloader.append, append_var)))
else:
err = "%s:%d: more than one bootloader specified" \
% (confpath, lineno)
raise KickStartError(err)

View File

@@ -0,0 +1,260 @@
#
# Copyright (c) 2013, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This module provides a place to collect various wic-related utils
# for the OpenEmbedded Image Tools.
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
#
"""Miscellaneous functions."""
import logging
import os
import re
import subprocess
from collections import defaultdict
from distutils import spawn
from wic import WicError
logger = logging.getLogger('wic')
# executable -> recipe pairs for exec_native_cmd
NATIVE_RECIPES = {"bmaptool": "bmap-tools",
"grub-mkimage": "grub-efi",
"isohybrid": "syslinux",
"mcopy": "mtools",
"mdel" : "mtools",
"mdeltree" : "mtools",
"mdir" : "mtools",
"mkdosfs": "dosfstools",
"mkisofs": "cdrtools",
"mkfs.btrfs": "btrfs-tools",
"mkfs.ext2": "e2fsprogs",
"mkfs.ext3": "e2fsprogs",
"mkfs.ext4": "e2fsprogs",
"mkfs.vfat": "dosfstools",
"mksquashfs": "squashfs-tools",
"mkswap": "util-linux",
"mmd": "mtools",
"parted": "parted",
"sfdisk": "util-linux",
"sgdisk": "gptfdisk",
"syslinux": "syslinux",
"tar": "tar"
}
def runtool(cmdln_or_args):
""" wrapper for most of the subprocess calls
input:
cmdln_or_args: can be both args and cmdln str (shell=True)
return:
rc, output
"""
if isinstance(cmdln_or_args, list):
cmd = cmdln_or_args[0]
shell = False
else:
import shlex
cmd = shlex.split(cmdln_or_args)[0]
shell = True
sout = subprocess.PIPE
serr = subprocess.STDOUT
try:
process = subprocess.Popen(cmdln_or_args, stdout=sout,
stderr=serr, shell=shell)
sout, serr = process.communicate()
# combine stdout and stderr, filter None out and decode
out = ''.join([out.decode('utf-8') for out in [sout, serr] if out])
except OSError as err:
if err.errno == 2:
# [Errno 2] No such file or directory
raise WicError('Cannot run command: %s, lost dependency?' % cmd)
else:
raise # relay
return process.returncode, out
def _exec_cmd(cmd_and_args, as_shell=False):
"""
Execute command, catching stderr, stdout
Need to execute as_shell if the command uses wildcards
"""
logger.debug("_exec_cmd: %s", cmd_and_args)
args = cmd_and_args.split()
logger.debug(args)
if as_shell:
ret, out = runtool(cmd_and_args)
else:
ret, out = runtool(args)
out = out.strip()
if ret != 0:
raise WicError("_exec_cmd: %s returned '%s' instead of 0\noutput: %s" % \
(cmd_and_args, ret, out))
logger.debug("_exec_cmd: output for %s (rc = %d): %s",
cmd_and_args, ret, out)
return ret, out
def exec_cmd(cmd_and_args, as_shell=False):
"""
Execute command, return output
"""
return _exec_cmd(cmd_and_args, as_shell)[1]
def find_executable(cmd, paths):
recipe = cmd
if recipe in NATIVE_RECIPES:
recipe = NATIVE_RECIPES[recipe]
provided = get_bitbake_var("ASSUME_PROVIDED")
if provided and "%s-native" % recipe in provided:
return True
return spawn.find_executable(cmd, paths)
def exec_native_cmd(cmd_and_args, native_sysroot, pseudo=""):
"""
Execute native command, catching stderr, stdout
Need to execute as_shell if the command uses wildcards
Always need to execute native commands as_shell
"""
# The reason -1 is used is because there may be "export" commands.
args = cmd_and_args.split(';')[-1].split()
logger.debug(args)
if pseudo:
cmd_and_args = pseudo + cmd_and_args
native_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin:%s/bin" % \
(native_sysroot, native_sysroot,
native_sysroot, native_sysroot)
native_cmd_and_args = "export PATH=%s:$PATH;%s" % \
(native_paths, cmd_and_args)
logger.debug("exec_native_cmd: %s", native_cmd_and_args)
# If the command isn't in the native sysroot say we failed.
if find_executable(args[0], native_paths):
ret, out = _exec_cmd(native_cmd_and_args, True)
else:
ret = 127
out = "can't find native executable %s in %s" % (args[0], native_paths)
prog = args[0]
# shell command-not-found
if ret == 127 \
or (pseudo and ret == 1 and out == "Can't find '%s' in $PATH." % prog):
msg = "A native program %s required to build the image "\
"was not found (see details above).\n\n" % prog
recipe = NATIVE_RECIPES.get(prog)
if recipe:
msg += "Please make sure wic-tools have %s-native in its DEPENDS, "\
"build it with 'bitbake wic-tools' and try again.\n" % recipe
else:
msg += "Wic failed to find a recipe to build native %s. Please "\
"file a bug against wic.\n" % prog
raise WicError(msg)
return ret, out
BOOTDD_EXTRA_SPACE = 16384
class BitbakeVars(defaultdict):
"""
Container for Bitbake variables.
"""
def __init__(self):
defaultdict.__init__(self, dict)
# default_image and vars_dir attributes should be set from outside
self.default_image = None
self.vars_dir = None
def _parse_line(self, line, image, matcher=re.compile(r"^([a-zA-Z0-9\-_+./~]+)=(.*)")):
"""
Parse one line from bitbake -e output or from .env file.
Put result key-value pair into the storage.
"""
if "=" not in line:
return
match = matcher.match(line)
if not match:
return
key, val = match.groups()
self[image][key] = val.strip('"')
def get_var(self, var, image=None, cache=True):
"""
Get bitbake variable from 'bitbake -e' output or from .env file.
This is a lazy method, i.e. it runs bitbake or parses file only when
only when variable is requested. It also caches results.
"""
if not image:
image = self.default_image
if image not in self:
if image and self.vars_dir:
fname = os.path.join(self.vars_dir, image + '.env')
if os.path.isfile(fname):
# parse .env file
with open(fname) as varsfile:
for line in varsfile:
self._parse_line(line, image)
else:
print("Couldn't get bitbake variable from %s." % fname)
print("File %s doesn't exist." % fname)
return
else:
# Get bitbake -e output
cmd = "bitbake -e"
if image:
cmd += " %s" % image
log_level = logger.getEffectiveLevel()
logger.setLevel(logging.INFO)
ret, lines = _exec_cmd(cmd)
logger.setLevel(log_level)
if ret:
logger.error("Couldn't get '%s' output.", cmd)
logger.error("Bitbake failed with error:\n%s\n", lines)
return
# Parse bitbake -e output
for line in lines.split('\n'):
self._parse_line(line, image)
# Make first image a default set of variables
if cache:
images = [key for key in self if key]
if len(images) == 1:
self[None] = self[image]
result = self[image].get(var)
if not cache:
self.pop(image, None)
return result
# Create BB_VARS singleton
BB_VARS = BitbakeVars()
def get_bitbake_var(var, image=None, cache=True):
"""
Provide old get_bitbake_var API by wrapping
get_var method of BB_VARS singleton.
"""
return BB_VARS.get_var(var, image, cache)

View File

@@ -0,0 +1,419 @@
#
# Copyright (c) 2013-2016 Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This module provides the OpenEmbedded partition object definitions.
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
# Ed Bartosh <ed.bartosh> (at] linux.intel.com>
import logging
import os
import uuid
from wic import WicError
from wic.misc import exec_cmd, exec_native_cmd, get_bitbake_var
from wic.pluginbase import PluginMgr
logger = logging.getLogger('wic')
class Partition():
def __init__(self, args, lineno):
self.args = args
self.active = args.active
self.align = args.align
self.disk = args.disk
self.device = None
self.extra_space = args.extra_space
self.exclude_path = args.exclude_path
self.include_path = args.include_path
self.change_directory = args.change_directory
self.fsopts = args.fsopts
self.fstype = args.fstype
self.label = args.label
self.use_label = args.use_label
self.mkfs_extraopts = args.mkfs_extraopts
self.mountpoint = args.mountpoint
self.no_table = args.no_table
self.num = None
self.offset = args.offset
self.overhead_factor = args.overhead_factor
self.part_name = args.part_name
self.part_type = args.part_type
self.rootfs_dir = args.rootfs_dir
self.size = args.size
self.fixed_size = args.fixed_size
self.source = args.source
self.sourceparams = args.sourceparams
self.system_id = args.system_id
self.use_uuid = args.use_uuid
self.uuid = args.uuid
self.fsuuid = args.fsuuid
self.type = args.type
self.lineno = lineno
self.source_file = ""
def get_extra_block_count(self, current_blocks):
"""
The --size param is reflected in self.size (in kB), and we already
have current_blocks (1k) blocks, calculate and return the
number of (1k) blocks we need to add to get to --size, 0 if
we're already there or beyond.
"""
logger.debug("Requested partition size for %s: %d",
self.mountpoint, self.size)
if not self.size:
return 0
requested_blocks = self.size
logger.debug("Requested blocks %d, current_blocks %d",
requested_blocks, current_blocks)
if requested_blocks > current_blocks:
return requested_blocks - current_blocks
else:
return 0
def get_rootfs_size(self, actual_rootfs_size=0):
"""
Calculate the required size of rootfs taking into consideration
--size/--fixed-size flags as well as overhead and extra space, as
specified in kickstart file. Raises an error if the
`actual_rootfs_size` is larger than fixed-size rootfs.
"""
if self.fixed_size:
rootfs_size = self.fixed_size
if actual_rootfs_size > rootfs_size:
raise WicError("Actual rootfs size (%d kB) is larger than "
"allowed size %d kB" %
(actual_rootfs_size, rootfs_size))
else:
extra_blocks = self.get_extra_block_count(actual_rootfs_size)
if extra_blocks < self.extra_space:
extra_blocks = self.extra_space
rootfs_size = actual_rootfs_size + extra_blocks
rootfs_size *= self.overhead_factor
logger.debug("Added %d extra blocks to %s to get to %d total blocks",
extra_blocks, self.mountpoint, rootfs_size)
return rootfs_size
@property
def disk_size(self):
"""
Obtain on-disk size of partition taking into consideration
--size/--fixed-size options.
"""
return self.fixed_size if self.fixed_size else self.size
def prepare(self, creator, cr_workdir, oe_builddir, rootfs_dir,
bootimg_dir, kernel_dir, native_sysroot):
"""
Prepare content for individual partitions, depending on
partition command parameters.
"""
if not self.source:
if not self.size and not self.fixed_size:
raise WicError("The %s partition has a size of zero. Please "
"specify a non-zero --size/--fixed-size for that "
"partition." % self.mountpoint)
if self.fstype == "swap":
self.prepare_swap_partition(cr_workdir, oe_builddir,
native_sysroot)
self.source_file = "%s/fs.%s" % (cr_workdir, self.fstype)
else:
if self.fstype == 'squashfs':
raise WicError("It's not possible to create empty squashfs "
"partition '%s'" % (self.mountpoint))
rootfs = "%s/fs_%s.%s.%s" % (cr_workdir, self.label,
self.lineno, self.fstype)
if os.path.isfile(rootfs):
os.remove(rootfs)
prefix = "ext" if self.fstype.startswith("ext") else self.fstype
method = getattr(self, "prepare_empty_partition_" + prefix)
method(rootfs, oe_builddir, native_sysroot)
self.source_file = rootfs
return
plugins = PluginMgr.get_plugins('source')
if self.source not in plugins:
raise WicError("The '%s' --source specified for %s doesn't exist.\n\t"
"See 'wic list source-plugins' for a list of available"
" --sources.\n\tSee 'wic help source-plugins' for "
"details on adding a new source plugin." %
(self.source, self.mountpoint))
srcparams_dict = {}
if self.sourceparams:
# Split sourceparams string of the form key1=val1[,key2=val2,...]
# into a dict. Also accepts valueless keys i.e. without =
splitted = self.sourceparams.split(',')
srcparams_dict = dict(par.split('=', 1) for par in splitted if par)
plugin = PluginMgr.get_plugins('source')[self.source]
plugin.do_configure_partition(self, srcparams_dict, creator,
cr_workdir, oe_builddir, bootimg_dir,
kernel_dir, native_sysroot)
plugin.do_stage_partition(self, srcparams_dict, creator,
cr_workdir, oe_builddir, bootimg_dir,
kernel_dir, native_sysroot)
plugin.do_prepare_partition(self, srcparams_dict, creator,
cr_workdir, oe_builddir, bootimg_dir,
kernel_dir, rootfs_dir, native_sysroot)
plugin.do_post_partition(self, srcparams_dict, creator,
cr_workdir, oe_builddir, bootimg_dir,
kernel_dir, rootfs_dir, native_sysroot)
# further processing required Partition.size to be an integer, make
# sure that it is one
if not isinstance(self.size, int):
raise WicError("Partition %s internal size is not an integer. "
"This a bug in source plugin %s and needs to be fixed." %
(self.mountpoint, self.source))
if self.fixed_size and self.size > self.fixed_size:
raise WicError("File system image of partition %s is "
"larger (%d kB) than its allowed size %d kB" %
(self.mountpoint, self.size, self.fixed_size))
def prepare_rootfs(self, cr_workdir, oe_builddir, rootfs_dir,
native_sysroot, real_rootfs = True, pseudo_dir = None):
"""
Prepare content for a rootfs partition i.e. create a partition
and fill it from a /rootfs dir.
Currently handles ext2/3/4, btrfs, vfat and squashfs.
"""
p_prefix = os.environ.get("PSEUDO_PREFIX", "%s/usr" % native_sysroot)
if (pseudo_dir):
pseudo = "export PSEUDO_PREFIX=%s;" % p_prefix
pseudo += "export PSEUDO_LOCALSTATEDIR=%s;" % pseudo_dir
pseudo += "export PSEUDO_PASSWD=%s;" % rootfs_dir
pseudo += "export PSEUDO_NOSYMLINKEXP=1;"
pseudo += "%s " % get_bitbake_var("FAKEROOTCMD")
else:
pseudo = None
rootfs = "%s/rootfs_%s.%s.%s" % (cr_workdir, self.label,
self.lineno, self.fstype)
if os.path.isfile(rootfs):
os.remove(rootfs)
if not self.size and real_rootfs:
# The rootfs size is not set in .ks file so try to get it
# from bitbake variable
rsize_bb = get_bitbake_var('ROOTFS_SIZE')
rdir = get_bitbake_var('IMAGE_ROOTFS')
if rsize_bb and rdir == rootfs_dir:
# Bitbake variable ROOTFS_SIZE is calculated in
# Image._get_rootfs_size method from meta/lib/oe/image.py
# using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
# IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
self.size = int(round(float(rsize_bb)))
else:
# Bitbake variable ROOTFS_SIZE is not defined so compute it
# from the rootfs_dir size using the same logic found in
# get_rootfs_size() from meta/classes/image.bbclass
du_cmd = "du -ks %s" % rootfs_dir
out = exec_cmd(du_cmd)
self.size = int(out.split()[0])
prefix = "ext" if self.fstype.startswith("ext") else self.fstype
method = getattr(self, "prepare_rootfs_" + prefix)
method(rootfs, oe_builddir, rootfs_dir, native_sysroot, pseudo)
self.source_file = rootfs
# get the rootfs size in the right units for kickstart (kB)
du_cmd = "du -Lbks %s" % rootfs
out = exec_cmd(du_cmd)
self.size = int(out.split()[0])
def prepare_rootfs_ext(self, rootfs, oe_builddir, rootfs_dir,
native_sysroot, pseudo):
"""
Prepare content for an ext2/3/4 rootfs partition.
"""
du_cmd = "du -ks %s" % rootfs_dir
out = exec_cmd(du_cmd)
actual_rootfs_size = int(out.split()[0])
rootfs_size = self.get_rootfs_size(actual_rootfs_size)
with open(rootfs, 'w') as sparse:
os.ftruncate(sparse.fileno(), rootfs_size * 1024)
extraopts = self.mkfs_extraopts or "-F -i 8192"
label_str = ""
if self.label:
label_str = "-L %s" % self.label
mkfs_cmd = "mkfs.%s %s %s %s -U %s -d %s" % \
(self.fstype, extraopts, rootfs, label_str, self.fsuuid, rootfs_dir)
exec_native_cmd(mkfs_cmd, native_sysroot, pseudo=pseudo)
mkfs_cmd = "fsck.%s -pvfD %s" % (self.fstype, rootfs)
exec_native_cmd(mkfs_cmd, native_sysroot, pseudo=pseudo)
def prepare_rootfs_btrfs(self, rootfs, oe_builddir, rootfs_dir,
native_sysroot, pseudo):
"""
Prepare content for a btrfs rootfs partition.
"""
du_cmd = "du -ks %s" % rootfs_dir
out = exec_cmd(du_cmd)
actual_rootfs_size = int(out.split()[0])
rootfs_size = self.get_rootfs_size(actual_rootfs_size)
with open(rootfs, 'w') as sparse:
os.ftruncate(sparse.fileno(), rootfs_size * 1024)
label_str = ""
if self.label:
label_str = "-L %s" % self.label
mkfs_cmd = "mkfs.%s -b %d -r %s %s %s -U %s %s" % \
(self.fstype, rootfs_size * 1024, rootfs_dir, label_str,
self.mkfs_extraopts, self.fsuuid, rootfs)
exec_native_cmd(mkfs_cmd, native_sysroot, pseudo=pseudo)
def prepare_rootfs_msdos(self, rootfs, oe_builddir, rootfs_dir,
native_sysroot, pseudo):
"""
Prepare content for a msdos/vfat rootfs partition.
"""
du_cmd = "du -bks %s" % rootfs_dir
out = exec_cmd(du_cmd)
blocks = int(out.split()[0])
rootfs_size = self.get_rootfs_size(blocks)
label_str = "-n boot"
if self.label:
label_str = "-n %s" % self.label
size_str = ""
if self.fstype == 'msdos':
size_str = "-F 16" # FAT 16
extraopts = self.mkfs_extraopts or '-S 512'
dosfs_cmd = "mkdosfs %s -i %s %s %s -C %s %d" % \
(label_str, self.fsuuid, size_str, extraopts, rootfs,
rootfs_size)
exec_native_cmd(dosfs_cmd, native_sysroot)
mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (rootfs, rootfs_dir)
exec_native_cmd(mcopy_cmd, native_sysroot)
chmod_cmd = "chmod 644 %s" % rootfs
exec_cmd(chmod_cmd)
prepare_rootfs_vfat = prepare_rootfs_msdos
def prepare_rootfs_squashfs(self, rootfs, oe_builddir, rootfs_dir,
native_sysroot, pseudo):
"""
Prepare content for a squashfs rootfs partition.
"""
extraopts = self.mkfs_extraopts or '-noappend'
squashfs_cmd = "mksquashfs %s %s %s" % \
(rootfs_dir, rootfs, extraopts)
exec_native_cmd(squashfs_cmd, native_sysroot, pseudo=pseudo)
def prepare_empty_partition_ext(self, rootfs, oe_builddir,
native_sysroot):
"""
Prepare an empty ext2/3/4 partition.
"""
size = self.disk_size
with open(rootfs, 'w') as sparse:
os.ftruncate(sparse.fileno(), size * 1024)
extraopts = self.mkfs_extraopts or "-i 8192"
label_str = ""
if self.label:
label_str = "-L %s" % self.label
mkfs_cmd = "mkfs.%s -F %s %s -U %s %s" % \
(self.fstype, extraopts, label_str, self.fsuuid, rootfs)
exec_native_cmd(mkfs_cmd, native_sysroot)
def prepare_empty_partition_btrfs(self, rootfs, oe_builddir,
native_sysroot):
"""
Prepare an empty btrfs partition.
"""
size = self.disk_size
with open(rootfs, 'w') as sparse:
os.ftruncate(sparse.fileno(), size * 1024)
label_str = ""
if self.label:
label_str = "-L %s" % self.label
mkfs_cmd = "mkfs.%s -b %d %s -U %s %s %s" % \
(self.fstype, self.size * 1024, label_str, self.fsuuid,
self.mkfs_extraopts, rootfs)
exec_native_cmd(mkfs_cmd, native_sysroot)
def prepare_empty_partition_msdos(self, rootfs, oe_builddir,
native_sysroot):
"""
Prepare an empty vfat partition.
"""
blocks = self.disk_size
label_str = "-n boot"
if self.label:
label_str = "-n %s" % self.label
size_str = ""
if self.fstype == 'msdos':
size_str = "-F 16" # FAT 16
extraopts = self.mkfs_extraopts or '-S 512'
dosfs_cmd = "mkdosfs %s -i %s %s %s -C %s %d" % \
(label_str, self.fsuuid, extraopts, size_str, rootfs,
blocks)
exec_native_cmd(dosfs_cmd, native_sysroot)
chmod_cmd = "chmod 644 %s" % rootfs
exec_cmd(chmod_cmd)
prepare_empty_partition_vfat = prepare_empty_partition_msdos
def prepare_swap_partition(self, cr_workdir, oe_builddir, native_sysroot):
"""
Prepare a swap partition.
"""
path = "%s/fs.%s" % (cr_workdir, self.fstype)
with open(path, 'w') as sparse:
os.ftruncate(sparse.fileno(), self.size * 1024)
label_str = ""
if self.label:
label_str = "-L %s" % self.label
mkswap_cmd = "mkswap %s -U %s %s" % (label_str, self.fsuuid, path)
exec_native_cmd(mkswap_cmd, native_sysroot)

View File

@@ -0,0 +1,140 @@
#!/usr/bin/env python3
#
# Copyright (c) 2011 Intel, Inc.
#
# SPDX-License-Identifier: GPL-2.0-only
#
__all__ = ['ImagerPlugin', 'SourcePlugin']
import os
import logging
from collections import defaultdict
from importlib.machinery import SourceFileLoader
from wic import WicError
from wic.misc import get_bitbake_var
PLUGIN_TYPES = ["imager", "source"]
SCRIPTS_PLUGIN_DIR = ["scripts/lib/wic/plugins", "lib/wic/plugins"]
logger = logging.getLogger('wic')
PLUGINS = defaultdict(dict)
class PluginMgr:
_plugin_dirs = []
@classmethod
def get_plugins(cls, ptype):
"""Get dictionary of <plugin_name>:<class> pairs."""
if ptype not in PLUGIN_TYPES:
raise WicError('%s is not valid plugin type' % ptype)
# collect plugin directories
if not cls._plugin_dirs:
cls._plugin_dirs = [os.path.join(os.path.dirname(__file__), 'plugins')]
layers = get_bitbake_var("BBLAYERS") or ''
for layer_path in layers.split():
for script_plugin_dir in SCRIPTS_PLUGIN_DIR:
path = os.path.join(layer_path, script_plugin_dir)
path = os.path.abspath(os.path.expanduser(path))
if path not in cls._plugin_dirs and os.path.isdir(path):
cls._plugin_dirs.insert(0, path)
if ptype not in PLUGINS:
# load all ptype plugins
for pdir in cls._plugin_dirs:
ppath = os.path.join(pdir, ptype)
if os.path.isdir(ppath):
for fname in os.listdir(ppath):
if fname.endswith('.py'):
mname = fname[:-3]
mpath = os.path.join(ppath, fname)
logger.debug("loading plugin module %s", mpath)
SourceFileLoader(mname, mpath).load_module()
return PLUGINS.get(ptype)
class PluginMeta(type):
def __new__(cls, name, bases, attrs):
class_type = type.__new__(cls, name, bases, attrs)
if 'name' in attrs:
PLUGINS[class_type.wic_plugin_type][attrs['name']] = class_type
return class_type
class ImagerPlugin(metaclass=PluginMeta):
wic_plugin_type = "imager"
def do_create(self):
raise WicError("Method %s.do_create is not implemented" %
self.__class__.__name__)
class SourcePlugin(metaclass=PluginMeta):
wic_plugin_type = "source"
"""
The methods that can be implemented by --source plugins.
Any methods not implemented in a subclass inherit these.
"""
@classmethod
def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
bootimg_dir, kernel_dir, native_sysroot):
"""
Called after all partitions have been prepared and assembled into a
disk image. This provides a hook to allow finalization of a
disk image e.g. to write an MBR to it.
"""
logger.debug("SourcePlugin: do_install_disk: disk: %s", disk_name)
@classmethod
def do_stage_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
native_sysroot):
"""
Special content staging hook called before do_prepare_partition(),
normally empty.
Typically, a partition will just use the passed-in parame e.g
straight bootimg_dir, etc, but in some cases, things need to
be more tailored e.g. to use a deploy dir + /boot, etc. This
hook allows those files to be staged in a customized fashion.
Not that get_bitbake_var() allows you to acces non-standard
variables that you might want to use for this.
"""
logger.debug("SourcePlugin: do_stage_partition: part: %s", part)
@classmethod
def do_configure_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
native_sysroot):
"""
Called before do_prepare_partition(), typically used to create
custom configuration files for a partition, for example
syslinux or grub config files.
"""
logger.debug("SourcePlugin: do_configure_partition: part: %s", part)
@classmethod
def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir, rootfs_dir,
native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
"""
logger.debug("SourcePlugin: do_prepare_partition: part: %s", part)
@classmethod
def do_post_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir, rootfs_dir,
native_sysroot):
"""
Called after the partition is created. It is useful to add post
operations e.g. security signing the partition.
"""
logger.debug("SourcePlugin: do_post_partition: part: %s", part)

View File

@@ -0,0 +1,615 @@
#
# Copyright (c) 2013, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This implements the 'direct' imager plugin class for 'wic'
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
#
import logging
import os
import random
import shutil
import tempfile
import uuid
from time import strftime
from oe.path import copyhardlinktree
from wic import WicError
from wic.filemap import sparse_copy
from wic.ksparser import KickStart, KickStartError
from wic.pluginbase import PluginMgr, ImagerPlugin
from wic.misc import get_bitbake_var, exec_cmd, exec_native_cmd
logger = logging.getLogger('wic')
class DirectPlugin(ImagerPlugin):
"""
Install a system into a file containing a partitioned disk image.
An image file is formatted with a partition table, each partition
created from a rootfs or other OpenEmbedded build artifact and dd'ed
into the virtual disk. The disk image can subsequently be dd'ed onto
media and used on actual hardware.
"""
name = 'direct'
def __init__(self, wks_file, rootfs_dir, bootimg_dir, kernel_dir,
native_sysroot, oe_builddir, options):
try:
self.ks = KickStart(wks_file)
except KickStartError as err:
raise WicError(str(err))
# parse possible 'rootfs=name' items
self.rootfs_dir = dict(rdir.split('=') for rdir in rootfs_dir.split(' '))
self.bootimg_dir = bootimg_dir
self.kernel_dir = kernel_dir
self.native_sysroot = native_sysroot
self.oe_builddir = oe_builddir
self.outdir = options.outdir
self.compressor = options.compressor
self.bmap = options.bmap
self.no_fstab_update = options.no_fstab_update
self.original_fstab = None
self.name = "%s-%s" % (os.path.splitext(os.path.basename(wks_file))[0],
strftime("%Y%m%d%H%M"))
self.workdir = tempfile.mkdtemp(dir=self.outdir, prefix='tmp.wic.')
self._image = None
self.ptable_format = self.ks.bootloader.ptable
self.parts = self.ks.partitions
# as a convenience, set source to the boot partition source
# instead of forcing it to be set via bootloader --source
for part in self.parts:
if not self.ks.bootloader.source and part.mountpoint == "/boot":
self.ks.bootloader.source = part.source
break
image_path = self._full_path(self.workdir, self.parts[0].disk, "direct")
self._image = PartitionedImage(image_path, self.ptable_format,
self.parts, self.native_sysroot)
def do_create(self):
"""
Plugin entry point.
"""
try:
self.create()
self.assemble()
self.finalize()
self.print_info()
finally:
self.cleanup()
def _write_fstab(self, image_rootfs):
"""overriden to generate fstab (temporarily) in rootfs. This is called
from _create, make sure it doesn't get called from
BaseImage.create()
"""
if not image_rootfs:
return
fstab_path = image_rootfs + "/etc/fstab"
if not os.path.isfile(fstab_path):
return
with open(fstab_path) as fstab:
fstab_lines = fstab.readlines()
self.original_fstab = fstab_lines.copy()
if self._update_fstab(fstab_lines, self.parts):
with open(fstab_path, "w") as fstab:
fstab.writelines(fstab_lines)
else:
self.original_fstab = None
def _update_fstab(self, fstab_lines, parts):
"""Assume partition order same as in wks"""
updated = False
for part in parts:
if not part.realnum or not part.mountpoint \
or part.mountpoint == "/":
continue
if part.use_uuid:
if part.fsuuid:
# FAT UUID is different from others
if len(part.fsuuid) == 10:
device_name = "UUID=%s-%s" % \
(part.fsuuid[2:6], part.fsuuid[6:])
else:
device_name = "UUID=%s" % part.fsuuid
else:
device_name = "PARTUUID=%s" % part.uuid
elif part.use_label:
device_name = "LABEL=%s" % part.label
else:
# mmc device partitions are named mmcblk0p1, mmcblk0p2..
prefix = 'p' if part.disk.startswith('mmcblk') else ''
device_name = "/dev/%s%s%d" % (part.disk, prefix, part.realnum)
opts = part.fsopts if part.fsopts else "defaults"
line = "\t".join([device_name, part.mountpoint, part.fstype,
opts, "0", "0"]) + "\n"
fstab_lines.append(line)
updated = True
return updated
def _full_path(self, path, name, extention):
""" Construct full file path to a file we generate. """
return os.path.join(path, "%s-%s.%s" % (self.name, name, extention))
#
# Actual implemention
#
def create(self):
"""
For 'wic', we already have our build artifacts - we just create
filesystems from the artifacts directly and combine them into
a partitioned image.
"""
if not self.no_fstab_update:
self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
for part in self.parts:
# get rootfs size from bitbake variable if it's not set in .ks file
if not part.size:
# and if rootfs name is specified for the partition
image_name = self.rootfs_dir.get(part.rootfs_dir)
if image_name and os.path.sep not in image_name:
# Bitbake variable ROOTFS_SIZE is calculated in
# Image._get_rootfs_size method from meta/lib/oe/image.py
# using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
# IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name)
if rsize_bb:
part.size = int(round(float(rsize_bb)))
self._image.prepare(self)
self._image.layout_partitions()
self._image.create()
def assemble(self):
"""
Assemble partitions into disk image
"""
self._image.assemble()
def finalize(self):
"""
Finalize the disk image.
For example, prepare the image to be bootable by e.g.
creating and installing a bootloader configuration.
"""
source_plugin = self.ks.bootloader.source
disk_name = self.parts[0].disk
if source_plugin:
plugin = PluginMgr.get_plugins('source')[source_plugin]
plugin.do_install_disk(self._image, disk_name, self, self.workdir,
self.oe_builddir, self.bootimg_dir,
self.kernel_dir, self.native_sysroot)
full_path = self._image.path
# Generate .bmap
if self.bmap:
logger.debug("Generating bmap file for %s", disk_name)
python = os.path.join(self.native_sysroot, 'usr/bin/python3-native/python3')
bmaptool = os.path.join(self.native_sysroot, 'usr/bin/bmaptool')
exec_native_cmd("%s %s create %s -o %s.bmap" % \
(python, bmaptool, full_path, full_path), self.native_sysroot)
# Compress the image
if self.compressor:
logger.debug("Compressing disk %s with %s", disk_name, self.compressor)
exec_cmd("%s %s" % (self.compressor, full_path))
def print_info(self):
"""
Print the image(s) and artifacts used, for the user.
"""
msg = "The new image(s) can be found here:\n"
extension = "direct" + {"gzip": ".gz",
"bzip2": ".bz2",
"xz": ".xz",
None: ""}.get(self.compressor)
full_path = self._full_path(self.outdir, self.parts[0].disk, extension)
msg += ' %s\n\n' % full_path
msg += 'The following build artifacts were used to create the image(s):\n'
for part in self.parts:
if part.rootfs_dir is None:
continue
if part.mountpoint == '/':
suffix = ':'
else:
suffix = '["%s"]:' % (part.mountpoint or part.label)
rootdir = part.rootfs_dir
msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), rootdir)
msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
logger.info(msg)
@property
def rootdev(self):
"""
Get root device name to use as a 'root' parameter
in kernel command line.
Assume partition order same as in wks
"""
for part in self.parts:
if part.mountpoint == "/":
if part.uuid:
return "PARTUUID=%s" % part.uuid
else:
suffix = 'p' if part.disk.startswith('mmcblk') else ''
return "/dev/%s%s%-d" % (part.disk, suffix, part.realnum)
def cleanup(self):
if self._image:
self._image.cleanup()
# Move results to the output dir
if not os.path.exists(self.outdir):
os.makedirs(self.outdir)
for fname in os.listdir(self.workdir):
path = os.path.join(self.workdir, fname)
if os.path.isfile(path):
shutil.move(path, os.path.join(self.outdir, fname))
#Restore original fstab
if self.original_fstab:
fstab_path = self.rootfs_dir.get("ROOTFS_DIR") + "/etc/fstab"
with open(fstab_path, "w") as fstab:
fstab.writelines(self.original_fstab)
# remove work directory
shutil.rmtree(self.workdir, ignore_errors=True)
# Overhead of the MBR partitioning scheme (just one sector)
MBR_OVERHEAD = 1
# Overhead of the GPT partitioning scheme
GPT_OVERHEAD = 34
# Size of a sector in bytes
SECTOR_SIZE = 512
class PartitionedImage():
"""
Partitioned image in a file.
"""
def __init__(self, path, ptable_format, partitions, native_sysroot=None):
self.path = path # Path to the image file
self.numpart = 0 # Number of allocated partitions
self.realpart = 0 # Number of partitions in the partition table
self.primary_part_num = 0 # Number of primary partitions (msdos)
self.extendedpart = 0 # Create extended partition before this logical partition (msdos)
self.extended_size_sec = 0 # Size of exteded partition (msdos)
self.logical_part_cnt = 0 # Number of total logical paritions (msdos)
self.offset = 0 # Offset of next partition (in sectors)
self.min_size = 0 # Minimum required disk size to fit
# all partitions (in bytes)
self.ptable_format = ptable_format # Partition table format
# Disk system identifier
self.identifier = random.SystemRandom().randint(1, 0xffffffff)
self.partitions = partitions
self.partimages = []
# Size of a sector used in calculations
self.sector_size = SECTOR_SIZE
self.native_sysroot = native_sysroot
num_real_partitions = len([p for p in self.partitions if not p.no_table])
# calculate the real partition number, accounting for partitions not
# in the partition table and logical partitions
realnum = 0
for part in self.partitions:
if part.no_table:
part.realnum = 0
else:
realnum += 1
if self.ptable_format == 'msdos' and realnum > 3 and num_real_partitions > 4:
part.realnum = realnum + 1
continue
part.realnum = realnum
# generate parition and filesystem UUIDs
for part in self.partitions:
if not part.uuid and part.use_uuid:
if self.ptable_format == 'gpt':
part.uuid = str(uuid.uuid4())
else: # msdos partition table
part.uuid = '%08x-%02d' % (self.identifier, part.realnum)
if not part.fsuuid:
if part.fstype == 'vfat' or part.fstype == 'msdos':
part.fsuuid = '0x' + str(uuid.uuid4())[:8].upper()
else:
part.fsuuid = str(uuid.uuid4())
def prepare(self, imager):
"""Prepare an image. Call prepare method of all image partitions."""
for part in self.partitions:
# need to create the filesystems in order to get their
# sizes before we can add them and do the layout.
part.prepare(imager, imager.workdir, imager.oe_builddir,
imager.rootfs_dir, imager.bootimg_dir,
imager.kernel_dir, imager.native_sysroot)
# Converting kB to sectors for parted
part.size_sec = part.disk_size * 1024 // self.sector_size
def layout_partitions(self):
""" Layout the partitions, meaning calculate the position of every
partition on the disk. The 'ptable_format' parameter defines the
partition table format and may be "msdos". """
logger.debug("Assigning %s partitions to disks", self.ptable_format)
# The number of primary and logical partitions. Extended partition and
# partitions not listed in the table are not included.
num_real_partitions = len([p for p in self.partitions if not p.no_table])
# Go through partitions in the order they are added in .ks file
for num in range(len(self.partitions)):
part = self.partitions[num]
if self.ptable_format == 'msdos' and part.part_name:
raise WicError("setting custom partition name is not " \
"implemented for msdos partitions")
if self.ptable_format == 'msdos' and part.part_type:
# The --part-type can also be implemented for MBR partitions,
# in which case it would map to the 1-byte "partition type"
# filed at offset 3 of the partition entry.
raise WicError("setting custom partition type is not " \
"implemented for msdos partitions")
# Get the disk where the partition is located
self.numpart += 1
if not part.no_table:
self.realpart += 1
if self.numpart == 1:
if self.ptable_format == "msdos":
overhead = MBR_OVERHEAD
elif self.ptable_format == "gpt":
overhead = GPT_OVERHEAD
# Skip one sector required for the partitioning scheme overhead
self.offset += overhead
if self.ptable_format == "msdos":
if self.primary_part_num > 3 or \
(self.extendedpart == 0 and self.primary_part_num >= 3 and num_real_partitions > 4):
part.type = 'logical'
# Reserve a sector for EBR for every logical partition
# before alignment is performed.
if part.type == 'logical':
self.offset += 2
align_sectors = 0
if part.align:
# If not first partition and we do have alignment set we need
# to align the partition.
# FIXME: This leaves a empty spaces to the disk. To fill the
# gaps we could enlargea the previous partition?
# Calc how much the alignment is off.
align_sectors = self.offset % (part.align * 1024 // self.sector_size)
if align_sectors:
# If partition is not aligned as required, we need
# to move forward to the next alignment point
align_sectors = (part.align * 1024 // self.sector_size) - align_sectors
logger.debug("Realignment for %s%s with %s sectors, original"
" offset %s, target alignment is %sK.",
part.disk, self.numpart, align_sectors,
self.offset, part.align)
# increase the offset so we actually start the partition on right alignment
self.offset += align_sectors
if part.offset is not None:
offset = part.offset // self.sector_size
if offset * self.sector_size != part.offset:
raise WicError("Could not place %s%s at offset %d with sector size %d" % (part.disk, self.numpart, part.offset, self.sector_size))
delta = offset - self.offset
if delta < 0:
raise WicError("Could not place %s%s at offset %d: next free sector is %d (delta: %d)" % (part.disk, self.numpart, part.offset, self.offset, delta))
logger.debug("Skipping %d sectors to place %s%s at offset %dK",
delta, part.disk, self.numpart, part.offset)
self.offset = offset
part.start = self.offset
self.offset += part.size_sec
if not part.no_table:
part.num = self.realpart
else:
part.num = 0
if self.ptable_format == "msdos" and not part.no_table:
if part.type == 'logical':
self.logical_part_cnt += 1
part.num = self.logical_part_cnt + 4
if self.extendedpart == 0:
# Create extended partition as a primary partition
self.primary_part_num += 1
self.extendedpart = part.num
else:
self.extended_size_sec += align_sectors
self.extended_size_sec += part.size_sec + 2
else:
self.primary_part_num += 1
part.num = self.primary_part_num
logger.debug("Assigned %s to %s%d, sectors range %d-%d size %d "
"sectors (%d bytes).", part.mountpoint, part.disk,
part.num, part.start, self.offset - 1, part.size_sec,
part.size_sec * self.sector_size)
# Once all the partitions have been layed out, we can calculate the
# minumim disk size
self.min_size = self.offset
if self.ptable_format == "gpt":
self.min_size += GPT_OVERHEAD
self.min_size *= self.sector_size
def _create_partition(self, device, parttype, fstype, start, size):
""" Create a partition on an image described by the 'device' object. """
# Start is included to the size so we need to substract one from the end.
end = start + size - 1
logger.debug("Added '%s' partition, sectors %d-%d, size %d sectors",
parttype, start, end, size)
cmd = "parted -s %s unit s mkpart %s" % (device, parttype)
if fstype:
cmd += " %s" % fstype
cmd += " %d %d" % (start, end)
return exec_native_cmd(cmd, self.native_sysroot)
def create(self):
logger.debug("Creating sparse file %s", self.path)
with open(self.path, 'w') as sparse:
os.ftruncate(sparse.fileno(), self.min_size)
logger.debug("Initializing partition table for %s", self.path)
exec_native_cmd("parted -s %s mklabel %s" %
(self.path, self.ptable_format), self.native_sysroot)
logger.debug("Set disk identifier %x", self.identifier)
with open(self.path, 'r+b') as img:
img.seek(0x1B8)
img.write(self.identifier.to_bytes(4, 'little'))
logger.debug("Creating partitions")
for part in self.partitions:
if part.num == 0:
continue
if self.ptable_format == "msdos" and part.num == self.extendedpart:
# Create an extended partition (note: extended
# partition is described in MBR and contains all
# logical partitions). The logical partitions save a
# sector for an EBR just before the start of a
# partition. The extended partition must start one
# sector before the start of the first logical
# partition. This way the first EBR is inside of the
# extended partition. Since the extended partitions
# starts a sector before the first logical partition,
# add a sector at the back, so that there is enough
# room for all logical partitions.
self._create_partition(self.path, "extended",
None, part.start - 2,
self.extended_size_sec)
if part.fstype == "swap":
parted_fs_type = "linux-swap"
elif part.fstype == "vfat":
parted_fs_type = "fat32"
elif part.fstype == "msdos":
parted_fs_type = "fat16"
if not part.system_id:
part.system_id = '0x6' # FAT16
else:
# Type for ext2/ext3/ext4/btrfs
parted_fs_type = "ext2"
# Boot ROM of OMAP boards require vfat boot partition to have an
# even number of sectors.
if part.mountpoint == "/boot" and part.fstype in ["vfat", "msdos"] \
and part.size_sec % 2:
logger.debug("Subtracting one sector from '%s' partition to "
"get even number of sectors for the partition",
part.mountpoint)
part.size_sec -= 1
self._create_partition(self.path, part.type,
parted_fs_type, part.start, part.size_sec)
if part.part_name:
logger.debug("partition %d: set name to %s",
part.num, part.part_name)
exec_native_cmd("sgdisk --change-name=%d:%s %s" % \
(part.num, part.part_name,
self.path), self.native_sysroot)
if part.part_type:
logger.debug("partition %d: set type UID to %s",
part.num, part.part_type)
exec_native_cmd("sgdisk --typecode=%d:%s %s" % \
(part.num, part.part_type,
self.path), self.native_sysroot)
if part.uuid and self.ptable_format == "gpt":
logger.debug("partition %d: set UUID to %s",
part.num, part.uuid)
exec_native_cmd("sgdisk --partition-guid=%d:%s %s" % \
(part.num, part.uuid, self.path),
self.native_sysroot)
if part.label and self.ptable_format == "gpt":
logger.debug("partition %d: set name to %s",
part.num, part.label)
exec_native_cmd("parted -s %s name %d %s" % \
(self.path, part.num, part.label),
self.native_sysroot)
if part.active:
flag_name = "legacy_boot" if self.ptable_format == 'gpt' else "boot"
logger.debug("Set '%s' flag for partition '%s' on disk '%s'",
flag_name, part.num, self.path)
exec_native_cmd("parted -s %s set %d %s on" % \
(self.path, part.num, flag_name),
self.native_sysroot)
if part.system_id:
exec_native_cmd("sfdisk --part-type %s %s %s" % \
(self.path, part.num, part.system_id),
self.native_sysroot)
def cleanup(self):
pass
def assemble(self):
logger.debug("Installing partitions")
for part in self.partitions:
source = part.source_file
if source:
# install source_file contents into a partition
sparse_copy(source, self.path, seek=part.start * self.sector_size)
logger.debug("Installed %s in partition %d, sectors %d-%d, "
"size %d sectors", source, part.num, part.start,
part.start + part.size_sec - 1, part.size_sec)
partimage = self.path + '.p%d' % part.num
os.rename(source, partimage)
self.partimages.append(partimage)

View File

@@ -0,0 +1,213 @@
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# DESCRIPTION
# This implements the 'bootimg-biosplusefi' source plugin class for 'wic'
#
# AUTHORS
# William Bourque <wbourque [at) gmail.com>
import types
from wic.pluginbase import SourcePlugin
from importlib.machinery import SourceFileLoader
class BootimgBiosPlusEFIPlugin(SourcePlugin):
"""
Create MBR + EFI boot partition
This plugin creates a boot partition that contains both
legacy BIOS and EFI content. It will be able to boot from both.
This is useful when managing PC fleet with some older machines
without EFI support.
Note it is possible to create an image that can boot from both
legacy BIOS and EFI by defining two partitions : one with arg
--source bootimg-efi and another one with --source bootimg-pcbios.
However, this method has the obvious downside that it requires TWO
partitions to be created on the storage device.
Both partitions will also be marked as "bootable" which does not work on
most BIOS, has BIOS often uses the "bootable" flag to determine
what to boot. If you have such a BIOS, you need to manually remove the
"bootable" flag from the EFI partition for the drive to be bootable.
Having two partitions also seems to confuse wic : the content of
the first partition will be duplicated into the second, even though it
will not be used at all.
Also, unlike "isoimage-isohybrid" that also does BIOS and EFI, this plugin
allows you to have more than only a single rootfs partitions and does
not turn the rootfs into an initramfs RAM image.
This plugin is made to put everything into a single /boot partition so it
does not have the limitations listed above.
The plugin is made so it does tries not to reimplement what's already
been done in other plugins; as such it imports "bootimg-pcbios"
and "bootimg-efi".
Plugin "bootimg-pcbios" is used to generate legacy BIOS boot.
Plugin "bootimg-efi" is used to generate the UEFI boot. Note that it
requires a --sourceparams argument to know which loader to use; refer
to "bootimg-efi" code/documentation for the list of loader.
Imports are handled with "SourceFileLoader" from importlib as it is
otherwise very difficult to import module that has hyphen "-" in their
filename.
The SourcePlugin() methods used in the plugins (do_install_disk,
do_configure_partition, do_prepare_partition) are then called on both,
beginning by "bootimg-efi".
Plugin options, such as "--sourceparams" can still be passed to a
plugin, as long they does not cause issue in the other plugin.
Example wic configuration:
part /boot --source bootimg-biosplusefi --sourceparams="loader=grub-efi"\\
--ondisk sda --label os_boot --active --align 1024 --use-uuid
"""
name = 'bootimg-biosplusefi'
__PCBIOS_MODULE_NAME = "bootimg-pcbios"
__EFI_MODULE_NAME = "bootimg-efi"
__imgEFIObj = None
__imgBiosObj = None
@classmethod
def __init__(cls):
"""
Constructor (init)
"""
# XXX
# For some reasons, __init__ constructor is never called.
# Something to do with how pluginbase works?
cls.__instanciateSubClasses()
@classmethod
def __instanciateSubClasses(cls):
"""
"""
# Import bootimg-pcbios (class name "BootimgPcbiosPlugin")
modulePath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
cls.__PCBIOS_MODULE_NAME + ".py")
loader = SourceFileLoader(cls.__PCBIOS_MODULE_NAME, modulePath)
mod = types.ModuleType(loader.name)
loader.exec_module(mod)
cls.__imgBiosObj = mod.BootimgPcbiosPlugin()
# Import bootimg-efi (class name "BootimgEFIPlugin")
modulePath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
cls.__EFI_MODULE_NAME + ".py")
loader = SourceFileLoader(cls.__EFI_MODULE_NAME, modulePath)
mod = types.ModuleType(loader.name)
loader.exec_module(mod)
cls.__imgEFIObj = mod.BootimgEFIPlugin()
@classmethod
def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
bootimg_dir, kernel_dir, native_sysroot):
"""
Called after all partitions have been prepared and assembled into a
disk image.
"""
if ( (not cls.__imgEFIObj) or (not cls.__imgBiosObj) ):
cls.__instanciateSubClasses()
cls.__imgEFIObj.do_install_disk(
disk,
disk_name,
creator,
workdir,
oe_builddir,
bootimg_dir,
kernel_dir,
native_sysroot)
cls.__imgBiosObj.do_install_disk(
disk,
disk_name,
creator,
workdir,
oe_builddir,
bootimg_dir,
kernel_dir,
native_sysroot)
@classmethod
def do_configure_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
native_sysroot):
"""
Called before do_prepare_partition()
"""
if ( (not cls.__imgEFIObj) or (not cls.__imgBiosObj) ):
cls.__instanciateSubClasses()
cls.__imgEFIObj.do_configure_partition(
part,
source_params,
creator,
cr_workdir,
oe_builddir,
bootimg_dir,
kernel_dir,
native_sysroot)
cls.__imgBiosObj.do_configure_partition(
part,
source_params,
creator,
cr_workdir,
oe_builddir,
bootimg_dir,
kernel_dir,
native_sysroot)
@classmethod
def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
rootfs_dir, native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
"""
if ( (not cls.__imgEFIObj) or (not cls.__imgBiosObj) ):
cls.__instanciateSubClasses()
cls.__imgEFIObj.do_prepare_partition(
part,
source_params,
creator,
cr_workdir,
oe_builddir,
bootimg_dir,
kernel_dir,
rootfs_dir,
native_sysroot)
cls.__imgBiosObj.do_prepare_partition(
part,
source_params,
creator,
cr_workdir,
oe_builddir,
bootimg_dir,
kernel_dir,
rootfs_dir,
native_sysroot)

View File

@@ -0,0 +1,360 @@
#
# Copyright (c) 2014, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This implements the 'bootimg-efi' source plugin class for 'wic'
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
#
import logging
import os
import shutil
import re
from glob import glob
from wic import WicError
from wic.engine import get_custom_config
from wic.pluginbase import SourcePlugin
from wic.misc import (exec_cmd, exec_native_cmd,
get_bitbake_var, BOOTDD_EXTRA_SPACE)
logger = logging.getLogger('wic')
class BootimgEFIPlugin(SourcePlugin):
"""
Create EFI boot partition.
This plugin supports GRUB 2 and systemd-boot bootloaders.
"""
name = 'bootimg-efi'
@classmethod
def do_configure_grubefi(cls, hdddir, creator, cr_workdir, source_params):
"""
Create loader-specific (grub-efi) config
"""
configfile = creator.ks.bootloader.configfile
custom_cfg = None
if configfile:
custom_cfg = get_custom_config(configfile)
if custom_cfg:
# Use a custom configuration for grub
grubefi_conf = custom_cfg
logger.debug("Using custom configuration file "
"%s for grub.cfg", configfile)
else:
raise WicError("configfile is specified but failed to "
"get it from %s." % configfile)
initrd = source_params.get('initrd')
if initrd:
bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
if not bootimg_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
initrds = initrd.split(';')
for rd in initrds:
cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir)
exec_cmd(cp_cmd, True)
else:
logger.debug("Ignoring missing initrd")
if not custom_cfg:
# Create grub configuration using parameters from wks file
bootloader = creator.ks.bootloader
title = source_params.get('title')
grubefi_conf = ""
grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
grubefi_conf += "default=boot\n"
grubefi_conf += "timeout=%s\n" % bootloader.timeout
grubefi_conf += "menuentry '%s'{\n" % (title if title else "boot")
kernel = get_bitbake_var("KERNEL_IMAGETYPE")
if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
if get_bitbake_var("INITRAMFS_IMAGE"):
kernel = "%s-%s.bin" % \
(get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
label = source_params.get('label')
label_conf = "root=%s" % creator.rootdev
if label:
label_conf = "LABEL=%s" % label
grubefi_conf += "linux /%s %s rootwait %s\n" \
% (kernel, label_conf, bootloader.append)
if initrd:
initrds = initrd.split(';')
grubefi_conf += "initrd"
for rd in initrds:
grubefi_conf += " /%s" % rd
grubefi_conf += "\n"
grubefi_conf += "}\n"
logger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg",
cr_workdir)
cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir, "w")
cfg.write(grubefi_conf)
cfg.close()
@classmethod
def do_configure_systemdboot(cls, hdddir, creator, cr_workdir, source_params):
"""
Create loader-specific systemd-boot/gummiboot config
"""
install_cmd = "install -d %s/loader" % hdddir
exec_cmd(install_cmd)
install_cmd = "install -d %s/loader/entries" % hdddir
exec_cmd(install_cmd)
bootloader = creator.ks.bootloader
loader_conf = ""
loader_conf += "default boot\n"
loader_conf += "timeout %d\n" % bootloader.timeout
initrd = source_params.get('initrd')
if initrd:
# obviously we need to have a common common deploy var
bootimg_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
if not bootimg_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
initrds = initrd.split(';')
for rd in initrds:
cp_cmd = "cp %s/%s %s" % (bootimg_dir, rd, hdddir)
exec_cmd(cp_cmd, True)
else:
logger.debug("Ignoring missing initrd")
logger.debug("Writing systemd-boot config "
"%s/hdd/boot/loader/loader.conf", cr_workdir)
cfg = open("%s/hdd/boot/loader/loader.conf" % cr_workdir, "w")
cfg.write(loader_conf)
cfg.close()
configfile = creator.ks.bootloader.configfile
custom_cfg = None
if configfile:
custom_cfg = get_custom_config(configfile)
if custom_cfg:
# Use a custom configuration for systemd-boot
boot_conf = custom_cfg
logger.debug("Using custom configuration file "
"%s for systemd-boots's boot.conf", configfile)
else:
raise WicError("configfile is specified but failed to "
"get it from %s.", configfile)
if not custom_cfg:
# Create systemd-boot configuration using parameters from wks file
kernel = get_bitbake_var("KERNEL_IMAGETYPE")
if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
if get_bitbake_var("INITRAMFS_IMAGE"):
kernel = "%s-%s.bin" % \
(get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
title = source_params.get('title')
boot_conf = ""
boot_conf += "title %s\n" % (title if title else "boot")
boot_conf += "linux /%s\n" % kernel
label = source_params.get('label')
label_conf = "LABEL=Boot root=%s" % creator.rootdev
if label:
label_conf = "LABEL=%s" % label
boot_conf += "options %s %s\n" % \
(label_conf, bootloader.append)
if initrd:
initrds = initrd.split(';')
for rd in initrds:
boot_conf += "initrd /%s\n" % rd
logger.debug("Writing systemd-boot config "
"%s/hdd/boot/loader/entries/boot.conf", cr_workdir)
cfg = open("%s/hdd/boot/loader/entries/boot.conf" % cr_workdir, "w")
cfg.write(boot_conf)
cfg.close()
@classmethod
def do_configure_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
native_sysroot):
"""
Called before do_prepare_partition(), creates loader-specific config
"""
hdddir = "%s/hdd/boot" % cr_workdir
install_cmd = "install -d %s/EFI/BOOT" % hdddir
exec_cmd(install_cmd)
try:
if source_params['loader'] == 'grub-efi':
cls.do_configure_grubefi(hdddir, creator, cr_workdir, source_params)
elif source_params['loader'] == 'systemd-boot':
cls.do_configure_systemdboot(hdddir, creator, cr_workdir, source_params)
else:
raise WicError("unrecognized bootimg-efi loader: %s" % source_params['loader'])
except KeyError:
raise WicError("bootimg-efi requires a loader, none specified")
if get_bitbake_var("IMAGE_BOOT_FILES") is None:
logger.debug('No boot files defined in IMAGE_BOOT_FILES')
else:
boot_files = None
for (fmt, id) in (("_uuid-%s", part.uuid), ("_label-%s", part.label), (None, None)):
if fmt:
var = fmt % id
else:
var = ""
boot_files = get_bitbake_var("IMAGE_BOOT_FILES" + var)
if boot_files:
break
logger.debug('Boot files: %s', boot_files)
# list of tuples (src_name, dst_name)
deploy_files = []
for src_entry in re.findall(r'[\w;\-\./\*]+', boot_files):
if ';' in src_entry:
dst_entry = tuple(src_entry.split(';'))
if not dst_entry[0] or not dst_entry[1]:
raise WicError('Malformed boot file entry: %s' % src_entry)
else:
dst_entry = (src_entry, src_entry)
logger.debug('Destination entry: %r', dst_entry)
deploy_files.append(dst_entry)
cls.install_task = [];
for deploy_entry in deploy_files:
src, dst = deploy_entry
if '*' in src:
# by default install files under their basename
entry_name_fn = os.path.basename
if dst != src:
# unless a target name was given, then treat name
# as a directory and append a basename
entry_name_fn = lambda name: \
os.path.join(dst,
os.path.basename(name))
srcs = glob(os.path.join(kernel_dir, src))
logger.debug('Globbed sources: %s', ', '.join(srcs))
for entry in srcs:
src = os.path.relpath(entry, kernel_dir)
entry_dst_name = entry_name_fn(entry)
cls.install_task.append((src, entry_dst_name))
else:
cls.install_task.append((src, dst))
@classmethod
def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
rootfs_dir, native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
In this case, prepare content for an EFI (grub) boot partition.
"""
if not kernel_dir:
kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
if not kernel_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
staging_kernel_dir = kernel_dir
hdddir = "%s/hdd/boot" % cr_workdir
kernel = get_bitbake_var("KERNEL_IMAGETYPE")
if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
if get_bitbake_var("INITRAMFS_IMAGE"):
kernel = "%s-%s.bin" % \
(get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
install_cmd = "install -m 0644 %s/%s %s/%s" % \
(staging_kernel_dir, kernel, hdddir, kernel)
exec_cmd(install_cmd)
if get_bitbake_var("IMAGE_BOOT_FILES"):
for src_path, dst_path in cls.install_task:
install_cmd = "install -m 0644 -D %s %s" \
% (os.path.join(kernel_dir, src_path),
os.path.join(hdddir, dst_path))
exec_cmd(install_cmd)
try:
if source_params['loader'] == 'grub-efi':
shutil.copyfile("%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir,
"%s/grub.cfg" % cr_workdir)
for mod in [x for x in os.listdir(kernel_dir) if x.startswith("grub-efi-")]:
cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[9:])
exec_cmd(cp_cmd, True)
shutil.move("%s/grub.cfg" % cr_workdir,
"%s/hdd/boot/EFI/BOOT/grub.cfg" % cr_workdir)
elif source_params['loader'] == 'systemd-boot':
for mod in [x for x in os.listdir(kernel_dir) if x.startswith("systemd-")]:
cp_cmd = "cp %s/%s %s/EFI/BOOT/%s" % (kernel_dir, mod, hdddir, mod[8:])
exec_cmd(cp_cmd, True)
else:
raise WicError("unrecognized bootimg-efi loader: %s" %
source_params['loader'])
except KeyError:
raise WicError("bootimg-efi requires a loader, none specified")
startup = os.path.join(kernel_dir, "startup.nsh")
if os.path.exists(startup):
cp_cmd = "cp %s %s/" % (startup, hdddir)
exec_cmd(cp_cmd, True)
du_cmd = "du -bks %s" % hdddir
out = exec_cmd(du_cmd)
blocks = int(out.split()[0])
extra_blocks = part.get_extra_block_count(blocks)
if extra_blocks < BOOTDD_EXTRA_SPACE:
extra_blocks = BOOTDD_EXTRA_SPACE
blocks += extra_blocks
logger.debug("Added %d extra blocks to %s to get to %d total blocks",
extra_blocks, part.mountpoint, blocks)
# dosfs image, created by mkdosfs
bootimg = "%s/boot.img" % cr_workdir
label = part.label if part.label else "ESP"
dosfs_cmd = "mkdosfs -n %s -i %s -C %s %d" % \
(label, part.fsuuid, bootimg, blocks)
exec_native_cmd(dosfs_cmd, native_sysroot)
mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
exec_native_cmd(mcopy_cmd, native_sysroot)
chmod_cmd = "chmod 644 %s" % bootimg
exec_cmd(chmod_cmd)
du_cmd = "du -Lbks %s" % bootimg
out = exec_cmd(du_cmd)
bootimg_size = out.split()[0]
part.size = int(bootimg_size)
part.source_file = bootimg

View File

@@ -0,0 +1,194 @@
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This implements the 'bootimg-partition' source plugin class for
# 'wic'. The plugin creates an image of boot partition, copying over
# files listed in IMAGE_BOOT_FILES bitbake variable.
#
# AUTHORS
# Maciej Borzecki <maciej.borzecki (at] open-rnd.pl>
#
import logging
import os
import re
from glob import glob
from wic import WicError
from wic.engine import get_custom_config
from wic.pluginbase import SourcePlugin
from wic.misc import exec_cmd, get_bitbake_var
logger = logging.getLogger('wic')
class BootimgPartitionPlugin(SourcePlugin):
"""
Create an image of boot partition, copying over files
listed in IMAGE_BOOT_FILES bitbake variable.
"""
name = 'bootimg-partition'
@classmethod
def do_configure_partition(cls, part, source_params, cr, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
native_sysroot):
"""
Called before do_prepare_partition(), create u-boot specific boot config
"""
hdddir = "%s/boot.%d" % (cr_workdir, part.lineno)
install_cmd = "install -d %s" % hdddir
exec_cmd(install_cmd)
if not kernel_dir:
kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
if not kernel_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
boot_files = None
for (fmt, id) in (("_uuid-%s", part.uuid), ("_label-%s", part.label), (None, None)):
if fmt:
var = fmt % id
else:
var = ""
boot_files = get_bitbake_var("IMAGE_BOOT_FILES" + var)
if boot_files is not None:
break
if boot_files is None:
raise WicError('No boot files defined, IMAGE_BOOT_FILES unset for entry #%d' % part.lineno)
logger.debug('Boot files: %s', boot_files)
# list of tuples (src_name, dst_name)
deploy_files = []
for src_entry in re.findall(r'[\w;\-\./\*]+', boot_files):
if ';' in src_entry:
dst_entry = tuple(src_entry.split(';'))
if not dst_entry[0] or not dst_entry[1]:
raise WicError('Malformed boot file entry: %s' % src_entry)
else:
dst_entry = (src_entry, src_entry)
logger.debug('Destination entry: %r', dst_entry)
deploy_files.append(dst_entry)
cls.install_task = [];
for deploy_entry in deploy_files:
src, dst = deploy_entry
if '*' in src:
# by default install files under their basename
entry_name_fn = os.path.basename
if dst != src:
# unless a target name was given, then treat name
# as a directory and append a basename
entry_name_fn = lambda name: \
os.path.join(dst,
os.path.basename(name))
srcs = glob(os.path.join(kernel_dir, src))
logger.debug('Globbed sources: %s', ', '.join(srcs))
for entry in srcs:
src = os.path.relpath(entry, kernel_dir)
entry_dst_name = entry_name_fn(entry)
cls.install_task.append((src, entry_dst_name))
else:
cls.install_task.append((src, dst))
if source_params.get('loader') != "u-boot":
return
configfile = cr.ks.bootloader.configfile
custom_cfg = None
if configfile:
custom_cfg = get_custom_config(configfile)
if custom_cfg:
# Use a custom configuration for extlinux.conf
extlinux_conf = custom_cfg
logger.debug("Using custom configuration file "
"%s for extlinux.cfg", configfile)
else:
raise WicError("configfile is specified but failed to "
"get it from %s." % configfile)
if not custom_cfg:
# The kernel types supported by the sysboot of u-boot
kernel_types = ["zImage", "Image", "fitImage", "uImage", "vmlinux"]
has_dtb = False
fdt_dir = '/'
kernel_name = None
# Find the kernel image name, from the highest precedence to lowest
for image in kernel_types:
for task in cls.install_task:
src, dst = task
if re.match(image, src):
kernel_name = os.path.join('/', dst)
break
if kernel_name:
break
for task in cls.install_task:
src, dst = task
# We suppose that all the dtb are in the same directory
if re.search(r'\.dtb', src) and fdt_dir == '/':
has_dtb = True
fdt_dir = os.path.join(fdt_dir, os.path.dirname(dst))
break
if not kernel_name:
raise WicError('No kernel file found')
# Compose the extlinux.conf
extlinux_conf = "default Yocto\n"
extlinux_conf += "label Yocto\n"
extlinux_conf += " kernel %s\n" % kernel_name
if has_dtb:
extlinux_conf += " fdtdir %s\n" % fdt_dir
bootloader = cr.ks.bootloader
extlinux_conf += "append root=%s rootwait %s\n" \
% (cr.rootdev, bootloader.append if bootloader.append else '')
install_cmd = "install -d %s/extlinux/" % hdddir
exec_cmd(install_cmd)
cfg = open("%s/extlinux/extlinux.conf" % hdddir, "w")
cfg.write(extlinux_conf)
cfg.close()
@classmethod
def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
rootfs_dir, native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
In this case, does the following:
- sets up a vfat partition
- copies all files listed in IMAGE_BOOT_FILES variable
"""
hdddir = "%s/boot.%d" % (cr_workdir, part.lineno)
if not kernel_dir:
kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
if not kernel_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
logger.debug('Kernel dir: %s', bootimg_dir)
for task in cls.install_task:
src_path, dst_path = task
logger.debug('Install %s as %s', src_path, dst_path)
install_cmd = "install -m 0644 -D %s %s" \
% (os.path.join(kernel_dir, src_path),
os.path.join(hdddir, dst_path))
exec_cmd(install_cmd)
logger.debug('Prepare boot partition using rootfs in %s', hdddir)
part.prepare_rootfs(cr_workdir, oe_builddir, hdddir,
native_sysroot, False)

View File

@@ -0,0 +1,207 @@
#
# Copyright (c) 2014, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This implements the 'bootimg-pcbios' source plugin class for 'wic'
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
#
import logging
import os
import re
from wic import WicError
from wic.engine import get_custom_config
from wic.pluginbase import SourcePlugin
from wic.misc import (exec_cmd, exec_native_cmd,
get_bitbake_var, BOOTDD_EXTRA_SPACE)
logger = logging.getLogger('wic')
class BootimgPcbiosPlugin(SourcePlugin):
"""
Create MBR boot partition and install syslinux on it.
"""
name = 'bootimg-pcbios'
@classmethod
def _get_bootimg_dir(cls, bootimg_dir, dirname):
"""
Check if dirname exists in default bootimg_dir or in STAGING_DIR.
"""
staging_datadir = get_bitbake_var("STAGING_DATADIR")
for result in (bootimg_dir, staging_datadir):
if os.path.exists("%s/%s" % (result, dirname)):
return result
# STAGING_DATADIR is expanded with MLPREFIX if multilib is enabled
# but dependency syslinux is still populated to original STAGING_DATADIR
nonarch_datadir = re.sub('/[^/]*recipe-sysroot', '/recipe-sysroot', staging_datadir)
if os.path.exists(os.path.join(nonarch_datadir, dirname)):
return nonarch_datadir
raise WicError("Couldn't find correct bootimg_dir, exiting")
@classmethod
def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
bootimg_dir, kernel_dir, native_sysroot):
"""
Called after all partitions have been prepared and assembled into a
disk image. In this case, we install the MBR.
"""
bootimg_dir = cls._get_bootimg_dir(bootimg_dir, 'syslinux')
mbrfile = "%s/syslinux/" % bootimg_dir
if creator.ptable_format == 'msdos':
mbrfile += "mbr.bin"
elif creator.ptable_format == 'gpt':
mbrfile += "gptmbr.bin"
else:
raise WicError("Unsupported partition table: %s" %
creator.ptable_format)
if not os.path.exists(mbrfile):
raise WicError("Couldn't find %s. If using the -e option, do you "
"have the right MACHINE set in local.conf? If not, "
"is the bootimg_dir path correct?" % mbrfile)
full_path = creator._full_path(workdir, disk_name, "direct")
logger.debug("Installing MBR on disk %s as %s with size %s bytes",
disk_name, full_path, disk.min_size)
dd_cmd = "dd if=%s of=%s conv=notrunc" % (mbrfile, full_path)
exec_cmd(dd_cmd, native_sysroot)
@classmethod
def do_configure_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
native_sysroot):
"""
Called before do_prepare_partition(), creates syslinux config
"""
hdddir = "%s/hdd/boot" % cr_workdir
install_cmd = "install -d %s" % hdddir
exec_cmd(install_cmd)
bootloader = creator.ks.bootloader
custom_cfg = None
if bootloader.configfile:
custom_cfg = get_custom_config(bootloader.configfile)
if custom_cfg:
# Use a custom configuration for grub
syslinux_conf = custom_cfg
logger.debug("Using custom configuration file %s "
"for syslinux.cfg", bootloader.configfile)
else:
raise WicError("configfile is specified but failed to "
"get it from %s." % bootloader.configfile)
if not custom_cfg:
# Create syslinux configuration using parameters from wks file
splash = os.path.join(cr_workdir, "/hdd/boot/splash.jpg")
if os.path.exists(splash):
splashline = "menu background splash.jpg"
else:
splashline = ""
syslinux_conf = ""
syslinux_conf += "PROMPT 0\n"
syslinux_conf += "TIMEOUT " + str(bootloader.timeout) + "\n"
syslinux_conf += "\n"
syslinux_conf += "ALLOWOPTIONS 1\n"
syslinux_conf += "SERIAL 0 115200\n"
syslinux_conf += "\n"
if splashline:
syslinux_conf += "%s\n" % splashline
syslinux_conf += "DEFAULT boot\n"
syslinux_conf += "LABEL boot\n"
kernel = "/vmlinuz"
syslinux_conf += "KERNEL " + kernel + "\n"
syslinux_conf += "APPEND label=boot root=%s %s\n" % \
(creator.rootdev, bootloader.append)
logger.debug("Writing syslinux config %s/hdd/boot/syslinux.cfg",
cr_workdir)
cfg = open("%s/hdd/boot/syslinux.cfg" % cr_workdir, "w")
cfg.write(syslinux_conf)
cfg.close()
@classmethod
def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
rootfs_dir, native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
In this case, prepare content for legacy bios boot partition.
"""
bootimg_dir = cls._get_bootimg_dir(bootimg_dir, 'syslinux')
staging_kernel_dir = kernel_dir
hdddir = "%s/hdd/boot" % cr_workdir
kernel = get_bitbake_var("KERNEL_IMAGETYPE")
if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
if get_bitbake_var("INITRAMFS_IMAGE"):
kernel = "%s-%s.bin" % \
(get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
cmds = ("install -m 0644 %s/%s %s/vmlinuz" %
(staging_kernel_dir, kernel, hdddir),
"install -m 444 %s/syslinux/ldlinux.sys %s/ldlinux.sys" %
(bootimg_dir, hdddir),
"install -m 0644 %s/syslinux/vesamenu.c32 %s/vesamenu.c32" %
(bootimg_dir, hdddir),
"install -m 444 %s/syslinux/libcom32.c32 %s/libcom32.c32" %
(bootimg_dir, hdddir),
"install -m 444 %s/syslinux/libutil.c32 %s/libutil.c32" %
(bootimg_dir, hdddir))
for install_cmd in cmds:
exec_cmd(install_cmd)
du_cmd = "du -bks %s" % hdddir
out = exec_cmd(du_cmd)
blocks = int(out.split()[0])
extra_blocks = part.get_extra_block_count(blocks)
if extra_blocks < BOOTDD_EXTRA_SPACE:
extra_blocks = BOOTDD_EXTRA_SPACE
blocks += extra_blocks
logger.debug("Added %d extra blocks to %s to get to %d total blocks",
extra_blocks, part.mountpoint, blocks)
# dosfs image, created by mkdosfs
bootimg = "%s/boot%s.img" % (cr_workdir, part.lineno)
dosfs_cmd = "mkdosfs -n boot -i %s -S 512 -C %s %d" % \
(part.fsuuid, bootimg, blocks)
exec_native_cmd(dosfs_cmd, native_sysroot)
mcopy_cmd = "mcopy -i %s -s %s/* ::/" % (bootimg, hdddir)
exec_native_cmd(mcopy_cmd, native_sysroot)
syslinux_cmd = "syslinux %s" % bootimg
exec_native_cmd(syslinux_cmd, native_sysroot)
chmod_cmd = "chmod 644 %s" % bootimg
exec_cmd(chmod_cmd)
du_cmd = "du -Lbks %s" % bootimg
out = exec_cmd(du_cmd)
bootimg_size = out.split()[0]
part.size = int(bootimg_size)
part.source_file = bootimg

View File

@@ -0,0 +1,447 @@
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This implements the 'isoimage-isohybrid' source plugin class for 'wic'
#
# AUTHORS
# Mihaly Varga <mihaly.varga (at] ni.com>
import glob
import logging
import os
import re
import shutil
from wic import WicError
from wic.engine import get_custom_config
from wic.pluginbase import SourcePlugin
from wic.misc import exec_cmd, exec_native_cmd, get_bitbake_var
logger = logging.getLogger('wic')
class IsoImagePlugin(SourcePlugin):
"""
Create a bootable ISO image
This plugin creates a hybrid, legacy and EFI bootable ISO image. The
generated image can be used on optical media as well as USB media.
Legacy boot uses syslinux and EFI boot uses grub or gummiboot (not
implemented yet) as bootloader. The plugin creates the directories required
by bootloaders and populates them by creating and configuring the
bootloader files.
Example kickstart file:
part /boot --source isoimage-isohybrid --sourceparams="loader=grub-efi, \\
image_name= IsoImage" --ondisk cd --label LIVECD
bootloader --timeout=10 --append=" "
In --sourceparams "loader" specifies the bootloader used for booting in EFI
mode, while "image_name" specifies the name of the generated image. In the
example above, wic creates an ISO image named IsoImage-cd.direct (default
extension added by direct imeger plugin) and a file named IsoImage-cd.iso
"""
name = 'isoimage-isohybrid'
@classmethod
def do_configure_syslinux(cls, creator, cr_workdir):
"""
Create loader-specific (syslinux) config
"""
splash = os.path.join(cr_workdir, "ISO/boot/splash.jpg")
if os.path.exists(splash):
splashline = "menu background splash.jpg"
else:
splashline = ""
bootloader = creator.ks.bootloader
syslinux_conf = ""
syslinux_conf += "PROMPT 0\n"
syslinux_conf += "TIMEOUT %s \n" % (bootloader.timeout or 10)
syslinux_conf += "\n"
syslinux_conf += "ALLOWOPTIONS 1\n"
syslinux_conf += "SERIAL 0 115200\n"
syslinux_conf += "\n"
if splashline:
syslinux_conf += "%s\n" % splashline
syslinux_conf += "DEFAULT boot\n"
syslinux_conf += "LABEL boot\n"
kernel = get_bitbake_var("KERNEL_IMAGETYPE")
if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
if get_bitbake_var("INITRAMFS_IMAGE"):
kernel = "%s-%s.bin" % \
(get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
syslinux_conf += "KERNEL /" + kernel + "\n"
syslinux_conf += "APPEND initrd=/initrd LABEL=boot %s\n" \
% bootloader.append
logger.debug("Writing syslinux config %s/ISO/isolinux/isolinux.cfg",
cr_workdir)
with open("%s/ISO/isolinux/isolinux.cfg" % cr_workdir, "w") as cfg:
cfg.write(syslinux_conf)
@classmethod
def do_configure_grubefi(cls, part, creator, target_dir):
"""
Create loader-specific (grub-efi) config
"""
configfile = creator.ks.bootloader.configfile
if configfile:
grubefi_conf = get_custom_config(configfile)
if grubefi_conf:
logger.debug("Using custom configuration file %s for grub.cfg",
configfile)
else:
raise WicError("configfile is specified "
"but failed to get it from %s", configfile)
else:
splash = os.path.join(target_dir, "splash.jpg")
if os.path.exists(splash):
splashline = "menu background splash.jpg"
else:
splashline = ""
bootloader = creator.ks.bootloader
grubefi_conf = ""
grubefi_conf += "serial --unit=0 --speed=115200 --word=8 "
grubefi_conf += "--parity=no --stop=1\n"
grubefi_conf += "default=boot\n"
grubefi_conf += "timeout=%s\n" % (bootloader.timeout or 10)
grubefi_conf += "\n"
grubefi_conf += "search --set=root --label %s " % part.label
grubefi_conf += "\n"
grubefi_conf += "menuentry 'boot'{\n"
kernel = get_bitbake_var("KERNEL_IMAGETYPE")
if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
if get_bitbake_var("INITRAMFS_IMAGE"):
kernel = "%s-%s.bin" % \
(get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
grubefi_conf += "linux /%s rootwait %s\n" \
% (kernel, bootloader.append)
grubefi_conf += "initrd /initrd \n"
grubefi_conf += "}\n"
if splashline:
grubefi_conf += "%s\n" % splashline
cfg_path = os.path.join(target_dir, "grub.cfg")
logger.debug("Writing grubefi config %s", cfg_path)
with open(cfg_path, "w") as cfg:
cfg.write(grubefi_conf)
@staticmethod
def _build_initramfs_path(rootfs_dir, cr_workdir):
"""
Create path for initramfs image
"""
initrd = get_bitbake_var("INITRD_LIVE") or get_bitbake_var("INITRD")
if not initrd:
initrd_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
if not initrd_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting.")
image_name = get_bitbake_var("IMAGE_BASENAME")
if not image_name:
raise WicError("Couldn't find IMAGE_BASENAME, exiting.")
image_type = get_bitbake_var("INITRAMFS_FSTYPES")
if not image_type:
raise WicError("Couldn't find INITRAMFS_FSTYPES, exiting.")
machine = os.path.basename(initrd_dir)
pattern = '%s/%s*%s.%s' % (initrd_dir, image_name, machine, image_type)
files = glob.glob(pattern)
if files:
initrd = files[0]
if not initrd or not os.path.exists(initrd):
# Create initrd from rootfs directory
initrd = "%s/initrd.cpio.gz" % cr_workdir
initrd_dir = "%s/INITRD" % cr_workdir
shutil.copytree("%s" % rootfs_dir, \
"%s" % initrd_dir, symlinks=True)
if os.path.isfile("%s/init" % rootfs_dir):
shutil.copy2("%s/init" % rootfs_dir, "%s/init" % initrd_dir)
elif os.path.lexists("%s/init" % rootfs_dir):
os.symlink(os.readlink("%s/init" % rootfs_dir), \
"%s/init" % initrd_dir)
elif os.path.isfile("%s/sbin/init" % rootfs_dir):
shutil.copy2("%s/sbin/init" % rootfs_dir, \
"%s" % initrd_dir)
elif os.path.lexists("%s/sbin/init" % rootfs_dir):
os.symlink(os.readlink("%s/sbin/init" % rootfs_dir), \
"%s/init" % initrd_dir)
else:
raise WicError("Couldn't find or build initrd, exiting.")
exec_cmd("cd %s && find . | cpio -o -H newc -R root:root >%s/initrd.cpio " \
% (initrd_dir, cr_workdir), as_shell=True)
exec_cmd("gzip -f -9 %s/initrd.cpio" % cr_workdir, as_shell=True)
shutil.rmtree(initrd_dir)
return initrd
@classmethod
def do_configure_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
native_sysroot):
"""
Called before do_prepare_partition(), creates loader-specific config
"""
isodir = "%s/ISO/" % cr_workdir
if os.path.exists(isodir):
shutil.rmtree(isodir)
install_cmd = "install -d %s " % isodir
exec_cmd(install_cmd)
# Overwrite the name of the created image
logger.debug(source_params)
if 'image_name' in source_params and \
source_params['image_name'].strip():
creator.name = source_params['image_name'].strip()
logger.debug("The name of the image is: %s", creator.name)
@classmethod
def do_prepare_partition(cls, part, source_params, creator, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
rootfs_dir, native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
In this case, prepare content for a bootable ISO image.
"""
isodir = "%s/ISO" % cr_workdir
if part.rootfs_dir is None:
if not 'ROOTFS_DIR' in rootfs_dir:
raise WicError("Couldn't find --rootfs-dir, exiting.")
rootfs_dir = rootfs_dir['ROOTFS_DIR']
else:
if part.rootfs_dir in rootfs_dir:
rootfs_dir = rootfs_dir[part.rootfs_dir]
elif part.rootfs_dir:
rootfs_dir = part.rootfs_dir
else:
raise WicError("Couldn't find --rootfs-dir=%s connection "
"or it is not a valid path, exiting." %
part.rootfs_dir)
if not os.path.isdir(rootfs_dir):
rootfs_dir = get_bitbake_var("IMAGE_ROOTFS")
if not os.path.isdir(rootfs_dir):
raise WicError("Couldn't find IMAGE_ROOTFS, exiting.")
part.rootfs_dir = rootfs_dir
deploy_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
img_iso_dir = get_bitbake_var("ISODIR")
# Remove the temporary file created by part.prepare_rootfs()
if os.path.isfile(part.source_file):
os.remove(part.source_file)
# Support using a different initrd other than default
if source_params.get('initrd'):
initrd = source_params['initrd']
if not deploy_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
cp_cmd = "cp %s/%s %s" % (deploy_dir, initrd, cr_workdir)
exec_cmd(cp_cmd)
else:
# Prepare initial ramdisk
initrd = "%s/initrd" % deploy_dir
if not os.path.isfile(initrd):
initrd = "%s/initrd" % img_iso_dir
if not os.path.isfile(initrd):
initrd = cls._build_initramfs_path(rootfs_dir, cr_workdir)
install_cmd = "install -m 0644 %s %s/initrd" % (initrd, isodir)
exec_cmd(install_cmd)
# Remove the temporary file created by _build_initramfs_path function
if os.path.isfile("%s/initrd.cpio.gz" % cr_workdir):
os.remove("%s/initrd.cpio.gz" % cr_workdir)
kernel = get_bitbake_var("KERNEL_IMAGETYPE")
if get_bitbake_var("INITRAMFS_IMAGE_BUNDLE") == "1":
if get_bitbake_var("INITRAMFS_IMAGE"):
kernel = "%s-%s.bin" % \
(get_bitbake_var("KERNEL_IMAGETYPE"), get_bitbake_var("INITRAMFS_LINK_NAME"))
install_cmd = "install -m 0644 %s/%s %s/%s" % \
(kernel_dir, kernel, isodir, kernel)
exec_cmd(install_cmd)
#Create bootloader for efi boot
try:
target_dir = "%s/EFI/BOOT" % isodir
if os.path.exists(target_dir):
shutil.rmtree(target_dir)
os.makedirs(target_dir)
if source_params['loader'] == 'grub-efi':
# Builds bootx64.efi/bootia32.efi if ISODIR didn't exist or
# didn't contains it
target_arch = get_bitbake_var("TARGET_SYS")
if not target_arch:
raise WicError("Coludn't find target architecture")
if re.match("x86_64", target_arch):
grub_src_image = "grub-efi-bootx64.efi"
grub_dest_image = "bootx64.efi"
elif re.match('i.86', target_arch):
grub_src_image = "grub-efi-bootia32.efi"
grub_dest_image = "bootia32.efi"
else:
raise WicError("grub-efi is incompatible with target %s" %
target_arch)
grub_target = os.path.join(target_dir, grub_dest_image)
if not os.path.isfile(grub_target):
grub_src = os.path.join(deploy_dir, grub_src_image)
if not os.path.exists(grub_src):
raise WicError("Grub loader %s is not found in %s. "
"Please build grub-efi first" % (grub_src_image, deploy_dir))
shutil.copy(grub_src, grub_target)
if not os.path.isfile(os.path.join(target_dir, "boot.cfg")):
cls.do_configure_grubefi(part, creator, target_dir)
else:
raise WicError("unrecognized bootimg-efi loader: %s" %
source_params['loader'])
except KeyError:
raise WicError("bootimg-efi requires a loader, none specified")
# Create efi.img that contains bootloader files for EFI booting
# if ISODIR didn't exist or didn't contains it
if os.path.isfile("%s/efi.img" % img_iso_dir):
install_cmd = "install -m 0644 %s/efi.img %s/efi.img" % \
(img_iso_dir, isodir)
exec_cmd(install_cmd)
else:
# Default to 100 blocks of extra space for file system overhead
esp_extra_blocks = int(source_params.get('esp_extra_blocks', '100'))
du_cmd = "du -bks %s/EFI" % isodir
out = exec_cmd(du_cmd)
blocks = int(out.split()[0])
blocks += esp_extra_blocks
logger.debug("Added 100 extra blocks to %s to get to %d "
"total blocks", part.mountpoint, blocks)
# dosfs image for EFI boot
bootimg = "%s/efi.img" % isodir
esp_label = source_params.get('esp_label', 'EFIimg')
dosfs_cmd = 'mkfs.vfat -n \'%s\' -S 512 -C %s %d' \
% (esp_label, bootimg, blocks)
exec_native_cmd(dosfs_cmd, native_sysroot)
mmd_cmd = "mmd -i %s ::/EFI" % bootimg
exec_native_cmd(mmd_cmd, native_sysroot)
mcopy_cmd = "mcopy -i %s -s %s/EFI/* ::/EFI/" \
% (bootimg, isodir)
exec_native_cmd(mcopy_cmd, native_sysroot)
chmod_cmd = "chmod 644 %s" % bootimg
exec_cmd(chmod_cmd)
# Prepare files for legacy boot
syslinux_dir = get_bitbake_var("STAGING_DATADIR")
if not syslinux_dir:
raise WicError("Couldn't find STAGING_DATADIR, exiting.")
if os.path.exists("%s/isolinux" % isodir):
shutil.rmtree("%s/isolinux" % isodir)
install_cmd = "install -d %s/isolinux" % isodir
exec_cmd(install_cmd)
cls.do_configure_syslinux(creator, cr_workdir)
install_cmd = "install -m 444 %s/syslinux/ldlinux.sys " % syslinux_dir
install_cmd += "%s/isolinux/ldlinux.sys" % isodir
exec_cmd(install_cmd)
install_cmd = "install -m 444 %s/syslinux/isohdpfx.bin " % syslinux_dir
install_cmd += "%s/isolinux/isohdpfx.bin" % isodir
exec_cmd(install_cmd)
install_cmd = "install -m 644 %s/syslinux/isolinux.bin " % syslinux_dir
install_cmd += "%s/isolinux/isolinux.bin" % isodir
exec_cmd(install_cmd)
install_cmd = "install -m 644 %s/syslinux/ldlinux.c32 " % syslinux_dir
install_cmd += "%s/isolinux/ldlinux.c32" % isodir
exec_cmd(install_cmd)
#create ISO image
iso_img = "%s/tempiso_img.iso" % cr_workdir
iso_bootimg = "isolinux/isolinux.bin"
iso_bootcat = "isolinux/boot.cat"
efi_img = "efi.img"
mkisofs_cmd = "mkisofs -V %s " % part.label
mkisofs_cmd += "-o %s -U " % iso_img
mkisofs_cmd += "-J -joliet-long -r -iso-level 2 -b %s " % iso_bootimg
mkisofs_cmd += "-c %s -no-emul-boot -boot-load-size 4 " % iso_bootcat
mkisofs_cmd += "-boot-info-table -eltorito-alt-boot "
mkisofs_cmd += "-eltorito-platform 0xEF -eltorito-boot %s " % efi_img
mkisofs_cmd += "-no-emul-boot %s " % isodir
logger.debug("running command: %s", mkisofs_cmd)
exec_native_cmd(mkisofs_cmd, native_sysroot)
shutil.rmtree(isodir)
du_cmd = "du -Lbks %s" % iso_img
out = exec_cmd(du_cmd)
isoimg_size = int(out.split()[0])
part.size = isoimg_size
part.source_file = iso_img
@classmethod
def do_install_disk(cls, disk, disk_name, creator, workdir, oe_builddir,
bootimg_dir, kernel_dir, native_sysroot):
"""
Called after all partitions have been prepared and assembled into a
disk image. In this case, we insert/modify the MBR using isohybrid
utility for booting via BIOS from disk storage devices.
"""
iso_img = "%s.p1" % disk.path
full_path = creator._full_path(workdir, disk_name, "direct")
full_path_iso = creator._full_path(workdir, disk_name, "iso")
isohybrid_cmd = "isohybrid -u %s" % iso_img
logger.debug("running command: %s", isohybrid_cmd)
exec_native_cmd(isohybrid_cmd, native_sysroot)
# Replace the image created by direct plugin with the one created by
# mkisofs command. This is necessary because the iso image created by
# mkisofs has a very specific MBR is system area of the ISO image, and
# direct plugin adds and configures an another MBR.
logger.debug("Replaceing the image created by direct plugin\n")
os.remove(disk.path)
shutil.copy2(iso_img, full_path_iso)
shutil.copy2(full_path_iso, full_path)

View File

@@ -0,0 +1,81 @@
#
# SPDX-License-Identifier: GPL-2.0-only
#
import logging
import os
from wic import WicError
from wic.pluginbase import SourcePlugin
from wic.misc import exec_cmd, get_bitbake_var
from wic.filemap import sparse_copy
logger = logging.getLogger('wic')
class RawCopyPlugin(SourcePlugin):
"""
Populate partition content from raw image file.
"""
name = 'rawcopy'
@staticmethod
def do_image_label(fstype, dst, label):
if fstype.startswith('ext'):
cmd = 'tune2fs -L %s %s' % (label, dst)
elif fstype in ('msdos', 'vfat'):
cmd = 'dosfslabel %s %s' % (dst, label)
elif fstype == 'btrfs':
cmd = 'btrfs filesystem label %s %s' % (dst, label)
elif fstype == 'swap':
cmd = 'mkswap -L %s %s' % (label, dst)
elif fstype == 'squashfs':
raise WicError("It's not possible to update a squashfs "
"filesystem label '%s'" % (label))
else:
raise WicError("Cannot update filesystem label: "
"Unknown fstype: '%s'" % (fstype))
exec_cmd(cmd)
@classmethod
def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
rootfs_dir, native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
"""
if not kernel_dir:
kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE")
if not kernel_dir:
raise WicError("Couldn't find DEPLOY_DIR_IMAGE, exiting")
logger.debug('Kernel dir: %s', kernel_dir)
if 'file' not in source_params:
raise WicError("No file specified")
src = os.path.join(kernel_dir, source_params['file'])
dst = os.path.join(cr_workdir, "%s.%s" % (os.path.basename(source_params['file']), part.lineno))
if not os.path.exists(os.path.dirname(dst)):
os.makedirs(os.path.dirname(dst))
if 'skip' in source_params:
sparse_copy(src, dst, skip=int(source_params['skip']))
else:
sparse_copy(src, dst)
# get the size in the right units for kickstart (kB)
du_cmd = "du -Lbks %s" % dst
out = exec_cmd(du_cmd)
filesize = int(out.split()[0])
if filesize > part.size:
part.size = filesize
if part.label:
RawCopyPlugin.do_image_label(part.fstype, dst, part.label)
part.source_file = dst

View File

@@ -0,0 +1,216 @@
#
# Copyright (c) 2014, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION
# This implements the 'rootfs' source plugin class for 'wic'
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
# Joao Henrique Ferreira de Freitas <joaohf (at] gmail.com>
#
import logging
import os
import shutil
import sys
from oe.path import copyhardlinktree
from pathlib import Path
from wic import WicError
from wic.pluginbase import SourcePlugin
from wic.misc import get_bitbake_var, exec_native_cmd
logger = logging.getLogger('wic')
class RootfsPlugin(SourcePlugin):
"""
Populate partition content from a rootfs directory.
"""
name = 'rootfs'
@staticmethod
def __validate_path(cmd, rootfs_dir, path):
if os.path.isabs(path):
logger.error("%s: Must be relative: %s" % (cmd, orig_path))
sys.exit(1)
# Disallow climbing outside of parent directory using '..',
# because doing so could be quite disastrous (we will delete the
# directory, or modify a directory outside OpenEmbedded).
full_path = os.path.realpath(os.path.join(rootfs_dir, path))
if not full_path.startswith(os.path.realpath(rootfs_dir)):
logger.error("%s: Must point inside the rootfs:" % (cmd, path))
sys.exit(1)
return full_path
@staticmethod
def __get_rootfs_dir(rootfs_dir):
if os.path.isdir(rootfs_dir):
return os.path.realpath(rootfs_dir)
image_rootfs_dir = get_bitbake_var("IMAGE_ROOTFS", rootfs_dir)
if not os.path.isdir(image_rootfs_dir):
raise WicError("No valid artifact IMAGE_ROOTFS from image "
"named %s has been found at %s, exiting." %
(rootfs_dir, image_rootfs_dir))
return os.path.realpath(image_rootfs_dir)
@staticmethod
def __get_pseudo(native_sysroot, rootfs, pseudo_dir):
pseudo = "export PSEUDO_PREFIX=%s/usr;" % native_sysroot
pseudo += "export PSEUDO_LOCALSTATEDIR=%s;" % pseudo_dir
pseudo += "export PSEUDO_PASSWD=%s;" % rootfs
pseudo += "export PSEUDO_NOSYMLINKEXP=1;"
pseudo += "%s " % get_bitbake_var("FAKEROOTCMD")
return pseudo
@classmethod
def do_prepare_partition(cls, part, source_params, cr, cr_workdir,
oe_builddir, bootimg_dir, kernel_dir,
krootfs_dir, native_sysroot):
"""
Called to do the actual content population for a partition i.e. it
'prepares' the partition to be incorporated into the image.
In this case, prepare content for legacy bios boot partition.
"""
if part.rootfs_dir is None:
if not 'ROOTFS_DIR' in krootfs_dir:
raise WicError("Couldn't find --rootfs-dir, exiting")
rootfs_dir = krootfs_dir['ROOTFS_DIR']
else:
if part.rootfs_dir in krootfs_dir:
rootfs_dir = krootfs_dir[part.rootfs_dir]
elif part.rootfs_dir:
rootfs_dir = part.rootfs_dir
else:
raise WicError("Couldn't find --rootfs-dir=%s connection or "
"it is not a valid path, exiting" % part.rootfs_dir)
part.rootfs_dir = cls.__get_rootfs_dir(rootfs_dir)
pseudo_dir = os.path.join(part.rootfs_dir, "../pseudo")
if not os.path.lexists(pseudo_dir):
logger.warn("%s folder does not exist. "
"Usernames and permissions will be invalid " % pseudo_dir)
pseudo_dir = None
new_rootfs = None
new_pseudo = None
# Handle excluded paths.
if part.exclude_path or part.include_path or part.change_directory:
# We need a new rootfs directory we can delete files from. Copy to
# workdir.
new_rootfs = os.path.realpath(os.path.join(cr_workdir, "rootfs%d" % part.lineno))
if os.path.lexists(new_rootfs):
shutil.rmtree(os.path.join(new_rootfs))
if part.change_directory:
cd = part.change_directory
if cd[-1] == '/':
cd = cd[:-1]
orig_dir = cls.__validate_path("--change-directory", part.rootfs_dir, cd)
else:
orig_dir = part.rootfs_dir
copyhardlinktree(orig_dir, new_rootfs)
# Convert the pseudo directory to its new location
if (pseudo_dir):
new_pseudo = os.path.realpath(
os.path.join(cr_workdir, "pseudo%d" % part.lineno))
if os.path.lexists(new_pseudo):
shutil.rmtree(new_pseudo)
os.mkdir(new_pseudo)
shutil.copy(os.path.join(pseudo_dir, "files.db"),
os.path.join(new_pseudo, "files.db"))
pseudo_cmd = "%s -B -m %s -M %s" % (cls.__get_pseudo(native_sysroot,
new_rootfs,
new_pseudo),
orig_dir, new_rootfs)
exec_native_cmd(pseudo_cmd, native_sysroot)
for in_path in part.include_path or []:
#parse arguments
include_path = in_path[0]
if len(in_path) > 2:
logger.error("'Invalid number of arguments for include-path")
sys.exit(1)
if len(in_path) == 2:
path = in_path[1]
else:
path = None
# Pack files to be included into a tar file.
# We need to create a tar file, because that way we can keep the
# permissions from the files even when they belong to different
# pseudo enviroments.
# If we simply copy files using copyhardlinktree/copytree... the
# copied files will belong to the user running wic.
tar_file = os.path.realpath(
os.path.join(cr_workdir, "include-path%d.tar" % part.lineno))
if os.path.isfile(include_path):
parent = os.path.dirname(os.path.realpath(include_path))
tar_cmd = "tar c --owner=root --group=root -f %s -C %s %s" % (
tar_file, parent, os.path.relpath(include_path, parent))
exec_native_cmd(tar_cmd, native_sysroot)
else:
if include_path in krootfs_dir:
include_path = krootfs_dir[include_path]
include_path = cls.__get_rootfs_dir(include_path)
include_pseudo = os.path.join(include_path, "../pseudo")
if os.path.lexists(include_pseudo):
pseudo = cls.__get_pseudo(native_sysroot, include_path,
include_pseudo)
tar_cmd = "tar cf %s -C %s ." % (tar_file, include_path)
else:
pseudo = None
tar_cmd = "tar c --owner=root --group=root -f %s -C %s ." % (
tar_file, include_path)
exec_native_cmd(tar_cmd, native_sysroot, pseudo)
#create destination
if path:
destination = cls.__validate_path("--include-path", new_rootfs, path)
Path(destination).mkdir(parents=True, exist_ok=True)
else:
destination = new_rootfs
#extract destination
untar_cmd = "tar xf %s -C %s" % (tar_file, destination)
if new_pseudo:
pseudo = cls.__get_pseudo(native_sysroot, new_rootfs, new_pseudo)
else:
pseudo = None
exec_native_cmd(untar_cmd, native_sysroot, pseudo)
os.remove(tar_file)
for orig_path in part.exclude_path or []:
path = orig_path
full_path = cls.__validate_path("--exclude-path", new_rootfs, path)
if not os.path.lexists(full_path):
continue
if path.endswith(os.sep):
# Delete content only.
for entry in os.listdir(full_path):
full_entry = os.path.join(full_path, entry)
if os.path.isdir(full_entry) and not os.path.islink(full_entry):
shutil.rmtree(full_entry)
else:
os.remove(full_entry)
else:
# Delete whole directory.
shutil.rmtree(full_path)
part.prepare_rootfs(cr_workdir, oe_builddir,
new_rootfs or part.rootfs_dir, native_sysroot,
pseudo_dir = new_pseudo or pseudo_dir)

544
yocto/meta-klipper/scripts/wic Executable file
View File

@@ -0,0 +1,544 @@
#!/usr/bin/env python3
#
# Copyright (c) 2013, Intel Corporation.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# DESCRIPTION 'wic' is the OpenEmbedded Image Creator that users can
# use to generate bootable images. Invoking it without any arguments
# will display help screens for the 'wic' command and list the
# available 'wic' subcommands. Invoking a subcommand without any
# arguments will likewise display help screens for the specified
# subcommand. Please use that interface for detailed help.
#
# AUTHORS
# Tom Zanussi <tom.zanussi (at] linux.intel.com>
#
__version__ = "0.2.0"
# Python Standard Library modules
import os
import sys
import argparse
import logging
import subprocess
from collections import namedtuple
from distutils import spawn
# External modules
scripts_path = os.path.dirname(os.path.realpath(__file__))
lib_path = scripts_path + '/lib'
sys.path.insert(0, lib_path)
import scriptpath
scriptpath.add_oe_lib_path()
# Check whether wic is running within eSDK environment
sdkroot = scripts_path
if os.environ.get('SDKTARGETSYSROOT'):
while sdkroot != '' and sdkroot != os.sep:
if os.path.exists(os.path.join(sdkroot, '.devtoolbase')):
# Set BUILDDIR for wic to work within eSDK
os.environ['BUILDDIR'] = sdkroot
# .devtoolbase only exists within eSDK
# If found, initialize bitbake path for eSDK environment and append to PATH
sdkroot = os.path.join(os.path.dirname(scripts_path), 'bitbake', 'bin')
os.environ['PATH'] += ":" + sdkroot
break
sdkroot = os.path.dirname(sdkroot)
bitbake_exe = spawn.find_executable('bitbake')
if bitbake_exe:
bitbake_path = scriptpath.add_bitbake_lib_path()
import bb
from wic import WicError
from wic.misc import get_bitbake_var, BB_VARS
from wic import engine
from wic import help as hlp
def wic_logger():
"""Create and convfigure wic logger."""
logger = logging.getLogger('wic')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(levelname)s: %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
logger = wic_logger()
def rootfs_dir_to_args(krootfs_dir):
"""
Get a rootfs_dir dict and serialize to string
"""
rootfs_dir = ''
for key, val in krootfs_dir.items():
rootfs_dir += ' '
rootfs_dir += '='.join([key, val])
return rootfs_dir.strip()
class RootfsArgAction(argparse.Action):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def __call__(self, parser, namespace, value, option_string=None):
if not "rootfs_dir" in vars(namespace) or \
not type(namespace.__dict__['rootfs_dir']) is dict:
namespace.__dict__['rootfs_dir'] = {}
if '=' in value:
(key, rootfs_dir) = value.split('=')
else:
key = 'ROOTFS_DIR'
rootfs_dir = value
namespace.__dict__['rootfs_dir'][key] = rootfs_dir
def wic_create_subcommand(options, usage_str):
"""
Command-line handling for image creation. The real work is done
by image.engine.wic_create()
"""
if options.build_rootfs and not bitbake_exe:
raise WicError("Can't build rootfs as bitbake is not in the $PATH")
if not options.image_name:
missed = []
for val, opt in [(options.rootfs_dir, 'rootfs-dir'),
(options.bootimg_dir, 'bootimg-dir'),
(options.kernel_dir, 'kernel-dir'),
(options.native_sysroot, 'native-sysroot')]:
if not val:
missed.append(opt)
if missed:
raise WicError("The following build artifacts are not specified: %s" %
", ".join(missed))
if options.image_name:
BB_VARS.default_image = options.image_name
else:
options.build_check = False
if options.vars_dir:
BB_VARS.vars_dir = options.vars_dir
if options.build_check and not engine.verify_build_env():
raise WicError("Couldn't verify build environment, exiting")
if options.debug:
logger.setLevel(logging.DEBUG)
if options.image_name:
if options.build_rootfs:
argv = ["bitbake", options.image_name]
if options.debug:
argv.append("--debug")
logger.info("Building rootfs...\n")
subprocess.check_call(argv)
rootfs_dir = get_bitbake_var("IMAGE_ROOTFS", options.image_name)
kernel_dir = get_bitbake_var("DEPLOY_DIR_IMAGE", options.image_name)
bootimg_dir = get_bitbake_var("STAGING_DATADIR", options.image_name)
native_sysroot = options.native_sysroot
if options.vars_dir and not native_sysroot:
native_sysroot = get_bitbake_var("RECIPE_SYSROOT_NATIVE", options.image_name)
else:
if options.build_rootfs:
raise WicError("Image name is not specified, exiting. "
"(Use -e/--image-name to specify it)")
native_sysroot = options.native_sysroot
if not options.vars_dir and (not native_sysroot or not os.path.isdir(native_sysroot)):
logger.info("Building wic-tools...\n")
subprocess.check_call(["bitbake", "wic-tools"])
native_sysroot = get_bitbake_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
if not native_sysroot:
raise WicError("Unable to find the location of the native tools sysroot")
wks_file = options.wks_file
if not wks_file.endswith(".wks"):
wks_file = engine.find_canned_image(scripts_path, wks_file)
if not wks_file:
raise WicError("No image named %s found, exiting. (Use 'wic list images' "
"to list available images, or specify a fully-qualified OE "
"kickstart (.wks) filename)" % options.wks_file)
if not options.image_name:
rootfs_dir = ''
if 'ROOTFS_DIR' in options.rootfs_dir:
rootfs_dir = options.rootfs_dir['ROOTFS_DIR']
bootimg_dir = options.bootimg_dir
kernel_dir = options.kernel_dir
native_sysroot = options.native_sysroot
if rootfs_dir and not os.path.isdir(rootfs_dir):
raise WicError("--rootfs-dir (-r) not found, exiting")
if not os.path.isdir(bootimg_dir):
raise WicError("--bootimg-dir (-b) not found, exiting")
if not os.path.isdir(kernel_dir):
raise WicError("--kernel-dir (-k) not found, exiting")
if not os.path.isdir(native_sysroot):
raise WicError("--native-sysroot (-n) not found, exiting")
else:
not_found = not_found_dir = ""
if not os.path.isdir(rootfs_dir):
(not_found, not_found_dir) = ("rootfs-dir", rootfs_dir)
elif not os.path.isdir(kernel_dir):
(not_found, not_found_dir) = ("kernel-dir", kernel_dir)
elif not os.path.isdir(native_sysroot):
(not_found, not_found_dir) = ("native-sysroot", native_sysroot)
if not_found:
if not not_found_dir:
not_found_dir = "Completely missing artifact - wrong image (.wks) used?"
logger.info("Build artifacts not found, exiting.")
logger.info(" (Please check that the build artifacts for the machine")
logger.info(" selected in local.conf actually exist and that they")
logger.info(" are the correct artifacts for the image (.wks file)).\n")
raise WicError("The artifact that couldn't be found was %s:\n %s", not_found, not_found_dir)
krootfs_dir = options.rootfs_dir
if krootfs_dir is None:
krootfs_dir = {}
krootfs_dir['ROOTFS_DIR'] = rootfs_dir
rootfs_dir = rootfs_dir_to_args(krootfs_dir)
logger.info("Creating image(s)...\n")
engine.wic_create(wks_file, rootfs_dir, bootimg_dir, kernel_dir,
native_sysroot, options)
def wic_list_subcommand(args, usage_str):
"""
Command-line handling for listing available images.
The real work is done by image.engine.wic_list()
"""
if not engine.wic_list(args, scripts_path):
raise WicError("Bad list arguments, exiting")
def wic_ls_subcommand(args, usage_str):
"""
Command-line handling for list content of images.
The real work is done by engine.wic_ls()
"""
engine.wic_ls(args, args.native_sysroot)
def wic_cp_subcommand(args, usage_str):
"""
Command-line handling for copying files/dirs to images.
The real work is done by engine.wic_cp()
"""
engine.wic_cp(args, args.native_sysroot)
def wic_rm_subcommand(args, usage_str):
"""
Command-line handling for removing files/dirs from images.
The real work is done by engine.wic_rm()
"""
engine.wic_rm(args, args.native_sysroot)
def wic_write_subcommand(args, usage_str):
"""
Command-line handling for writing images.
The real work is done by engine.wic_write()
"""
engine.wic_write(args, args.native_sysroot)
def wic_help_subcommand(args, usage_str):
"""
Command-line handling for help subcommand to keep the current
structure of the function definitions.
"""
pass
def wic_help_topic_subcommand(usage_str, help_str):
"""
Display function for help 'sub-subcommands'.
"""
print(help_str)
return
wic_help_topic_usage = """
"""
helptopics = {
"plugins": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_plugins_help],
"overview": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_overview_help],
"kickstart": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_kickstart_help],
"create": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_create_help],
"ls": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_ls_help],
"cp": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_cp_help],
"rm": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_rm_help],
"write": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_write_help],
"list": [wic_help_topic_subcommand,
wic_help_topic_usage,
hlp.wic_list_help]
}
def wic_init_parser_create(subparser):
subparser.add_argument("wks_file")
subparser.add_argument("-o", "--outdir", dest="outdir", default='.',
help="name of directory to create image in")
subparser.add_argument("-e", "--image-name", dest="image_name",
help="name of the image to use the artifacts from "
"e.g. core-image-sato")
subparser.add_argument("-r", "--rootfs-dir", action=RootfsArgAction,
help="path to the /rootfs dir to use as the "
".wks rootfs source")
subparser.add_argument("-b", "--bootimg-dir", dest="bootimg_dir",
help="path to the dir containing the boot artifacts "
"(e.g. /EFI or /syslinux dirs) to use as the "
".wks bootimg source")
subparser.add_argument("-k", "--kernel-dir", dest="kernel_dir",
help="path to the dir containing the kernel to use "
"in the .wks bootimg")
subparser.add_argument("-n", "--native-sysroot", dest="native_sysroot",
help="path to the native sysroot containing the tools "
"to use to build the image")
subparser.add_argument("-s", "--skip-build-check", dest="build_check",
action="store_false", default=True, help="skip the build check")
subparser.add_argument("-f", "--build-rootfs", action="store_true", help="build rootfs")
subparser.add_argument("-c", "--compress-with", choices=("gzip", "bzip2", "xz"),
dest='compressor',
help="compress image with specified compressor")
subparser.add_argument("-m", "--bmap", action="store_true", help="generate .bmap")
subparser.add_argument("--no-fstab-update" ,action="store_true",
help="Do not change fstab file.")
subparser.add_argument("-v", "--vars", dest='vars_dir',
help="directory with <image>.env files that store "
"bitbake variables")
subparser.add_argument("-D", "--debug", dest="debug", action="store_true",
default=False, help="output debug information")
subparser.add_argument("-i", "--imager", dest="imager",
default="direct", help="the wic imager plugin")
return
def wic_init_parser_list(subparser):
subparser.add_argument("list_type",
help="can be 'images' or 'source-plugins' "
"to obtain a list. "
"If value is a valid .wks image file")
subparser.add_argument("help_for", default=[], nargs='*',
help="If 'list_type' is a valid .wks image file "
"this value can be 'help' to show the help information "
"defined inside the .wks file")
return
def imgtype(arg):
"""
Custom type for ArgumentParser
Converts path spec to named tuple: (image, partition, path)
"""
image = arg
part = path = None
if ':' in image:
image, part = image.split(':')
if '/' in part:
part, path = part.split('/', 1)
if not path:
path = '/'
if not os.path.isfile(image):
err = "%s is not a regular file or symlink" % image
raise argparse.ArgumentTypeError(err)
return namedtuple('ImgType', 'image part path')(image, part, path)
def wic_init_parser_ls(subparser):
subparser.add_argument("path", type=imgtype,
help="image spec: <image>[:<vfat partition>[<path>]]")
subparser.add_argument("-n", "--native-sysroot",
help="path to the native sysroot containing the tools")
def imgpathtype(arg):
img = imgtype(arg)
if img.part is None:
raise argparse.ArgumentTypeError("partition number is not specified")
return img
def wic_init_parser_cp(subparser):
subparser.add_argument("src",
help="image spec: <image>:<vfat partition>[<path>] or <file>")
subparser.add_argument("dest",
help="image spec: <image>:<vfat partition>[<path>] or <file>")
subparser.add_argument("-n", "--native-sysroot",
help="path to the native sysroot containing the tools")
def wic_init_parser_rm(subparser):
subparser.add_argument("path", type=imgpathtype,
help="path: <image>:<vfat partition><path>")
subparser.add_argument("-n", "--native-sysroot",
help="path to the native sysroot containing the tools")
subparser.add_argument("-r", dest="recursive_delete", action="store_true", default=False,
help="remove directories and their contents recursively, "
" this only applies to ext* partition")
def expandtype(rules):
"""
Custom type for ArgumentParser
Converts expand rules to the dictionary {<partition>: size}
"""
if rules == 'auto':
return {}
result = {}
for rule in rules.split(','):
try:
part, size = rule.split(':')
except ValueError:
raise argparse.ArgumentTypeError("Incorrect rule format: %s" % rule)
if not part.isdigit():
raise argparse.ArgumentTypeError("Rule '%s': partition number must be integer" % rule)
# validate size
multiplier = 1
for suffix, mult in [('K', 1024), ('M', 1024 * 1024), ('G', 1024 * 1024 * 1024)]:
if size.upper().endswith(suffix):
multiplier = mult
size = size[:-1]
break
if not size.isdigit():
raise argparse.ArgumentTypeError("Rule '%s': size must be integer" % rule)
result[int(part)] = int(size) * multiplier
return result
def wic_init_parser_write(subparser):
subparser.add_argument("image",
help="path to the wic image")
subparser.add_argument("target",
help="target file or device")
subparser.add_argument("-e", "--expand", type=expandtype,
help="expand rules: auto or <partition>:<size>[,<partition>:<size>]")
subparser.add_argument("-n", "--native-sysroot",
help="path to the native sysroot containing the tools")
def wic_init_parser_help(subparser):
helpparsers = subparser.add_subparsers(dest='help_topic', help=hlp.wic_usage)
for helptopic in helptopics:
helpparsers.add_parser(helptopic, help=helptopics[helptopic][2])
return
subcommands = {
"create": [wic_create_subcommand,
hlp.wic_create_usage,
hlp.wic_create_help,
wic_init_parser_create],
"list": [wic_list_subcommand,
hlp.wic_list_usage,
hlp.wic_list_help,
wic_init_parser_list],
"ls": [wic_ls_subcommand,
hlp.wic_ls_usage,
hlp.wic_ls_help,
wic_init_parser_ls],
"cp": [wic_cp_subcommand,
hlp.wic_cp_usage,
hlp.wic_cp_help,
wic_init_parser_cp],
"rm": [wic_rm_subcommand,
hlp.wic_rm_usage,
hlp.wic_rm_help,
wic_init_parser_rm],
"write": [wic_write_subcommand,
hlp.wic_write_usage,
hlp.wic_write_help,
wic_init_parser_write],
"help": [wic_help_subcommand,
wic_help_topic_usage,
hlp.wic_help_help,
wic_init_parser_help]
}
def init_parser(parser):
parser.add_argument("--version", action="version",
version="%(prog)s {version}".format(version=__version__))
parser.add_argument("-D", "--debug", dest="debug", action="store_true",
default=False, help="output debug information")
subparsers = parser.add_subparsers(dest='command', help=hlp.wic_usage)
for subcmd in subcommands:
subparser = subparsers.add_parser(subcmd, help=subcommands[subcmd][2])
subcommands[subcmd][3](subparser)
class WicArgumentParser(argparse.ArgumentParser):
def format_help(self):
return hlp.wic_help
def main(argv):
parser = WicArgumentParser(
description="wic version %s" % __version__)
init_parser(parser)
args = parser.parse_args(argv)
if args.debug:
logger.setLevel(logging.DEBUG)
if "command" in vars(args):
if args.command == "help":
if args.help_topic is None:
parser.print_help()
elif args.help_topic in helptopics:
hlpt = helptopics[args.help_topic]
hlpt[0](hlpt[1], hlpt[2])
return 0
# validate wic cp src and dest parameter to identify which one of it is
# image and cast it into imgtype
if args.command == "cp":
if ":" in args.dest:
args.dest = imgtype(args.dest)
elif ":" in args.src:
args.src = imgtype(args.src)
else:
raise argparse.ArgumentTypeError("no image or partition number specified.")
return hlp.invoke_subcommand(args, parser, hlp.wic_help_usage, subcommands)
if __name__ == "__main__":
try:
sys.exit(main(sys.argv[1:]))
except WicError as err:
print()
logger.error(err)
sys.exit(1)

View File

@@ -0,0 +1,14 @@
# The disk layout used is:
#
# 0 -> 8*1024 - reserverd
# 8*1024 -> - arm combined spl/u-boot or aarch64 spl
# 40*1024 -> - aarch64 u-boot
# 2048*1024 -> BOOT_SPACE - bootloader and kernel
part spl --offset 8k --source=rawcopy --sourceparams="file=u-boot-sunxi-with-spl.bin" --no-table
part uboot --offset 2048k --active --source bootimg-partition --fstype=vfat --label BOOT --sourceparams="loader=u-boot"
part root --size 256M --align 2048 --source rawcopy --sourceparams="file=klipper-linux-rootfs-${MACHINE}.ext4" --fstype=ext4 --label ROOT
bootloader --ptable msdos --append="console=ttyS0,115200n8 rw rootfstype=ext4 init=/sbin/init root=/dev/mmcblk0p2"

1
yocto/meta-sunxi Submodule

Submodule yocto/meta-sunxi added at ea508b4f10