From 00c2ee2a66f1847fc046661dee3cb0030cb725ff Mon Sep 17 00:00:00 2001 From: Nate Choe Date: Fri, 8 Dec 2023 15:01:55 -0600 Subject: [PATCH] Add qcow2 support (#226) * Added qcow2 image support * Automatic file type conversion --- Dockerfile | 1 + readme.md | 9 ++ src/disk.sh | 235 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 188 insertions(+), 57 deletions(-) diff --git a/Dockerfile b/Dockerfile index 294cd415..a3239a7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN apt-get update && apt-get -y upgrade && \ ca-certificates \ netcat-openbsd \ qemu-system-x86 \ + qemu-utils \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/readme.md b/readme.md index 81685ed5..94a380bd 100644 --- a/readme.md +++ b/readme.md @@ -181,6 +181,15 @@ docker run -it --rm -e "BOOT=http://www.example.com/image.iso" --device=/dev/kvm Please note that even if you don't need DHCP, it's still recommended to enable this feature as it prevents NAT issues and increases performance by using a `macvtap` interface. + * ### How can I use qcow2 disk files? + + Add the following lines to your compose file + + ```yaml + environment: + DISK_FMT: "qcow2" + ``` + [build_url]: https://github.com/qemu-tools/qemu-docker/ [hub_url]: https://hub.docker.com/r/qemux/qemu-docker/ [tag_url]: https://hub.docker.com/r/qemux/qemu-docker/tags diff --git a/src/disk.sh b/src/disk.sh index c97460a2..954b90de 100644 --- a/src/disk.sh +++ b/src/disk.sh @@ -7,6 +7,7 @@ set -Eeuo pipefail : ${DISK_CACHE:='none'} # Caching mode, can be set to 'writeback' for better performance : ${DISK_DISCARD:='on'} # Controls whether unmap (TRIM) commands are passed to the host. : ${DISK_ROTATION:='1'} # Rotation rate, set to 1 for SSD storage and increase for HDD +: ${DISK_FMT:='raw'} # Disk file format, "raw" by default for backwards compatibility DISK_OPTS="" BOOT="$STORAGE/boot.img" @@ -18,37 +19,61 @@ if [ -f "$BOOT" ]; then -device scsi-cd,bus=scsi0.0,drive=cdrom0,bootindex=10" fi -addDisk () { +fmt2ext() { + local DISK_FMT=$1 + + case "${DISK_FMT,,}" in + qcow2) + echo "qcow2" + ;; + raw) + echo "img" + ;; + *) + error "Unrecognized disk format ${DISK_FMT}" && exit 88 + ;; + esac +} + +ext2fmt() { + local DISK_EXT=$1 + + case "${DISK_EXT,,}" in + qcow2) + echo "qcow2" + ;; + img) + echo "raw" + ;; + *) + error "Unrecognized file extension .${DISK_EXT}" && exit 90 + ;; + esac +} + +getSize() { + local DISK_FILE=$1 + qemu-img info "${DISK_FILE}" -f "${DISK_FMT}" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/' +} + +doResize() { local GB - local DIR local REQ local SPACE - local CUR_SIZE - local DATA_SIZE - local DISK_ID=$1 - local DISK_FILE=$2 - local DISK_DESC=$3 + local SPACE_GB + local DISK_FILE=$1 + local CUR_SIZE=$2 + local DATA_SIZE=$3 local DISK_SPACE=$4 - local DISK_INDEX=$5 - local DISK_ADDRESS=$6 - - DIR=$(dirname "${DISK_FILE}") - [ ! -d "${DIR}" ] && return 0 - - [ -z "$DISK_SPACE" ] && DISK_SPACE="16G" - DISK_SPACE=$(echo "${DISK_SPACE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') - DATA_SIZE=$(numfmt --from=iec "${DISK_SPACE}") + local DISK_DESC=$5 + local DISK_FMT=$6 - if [ -f "${DISK_FILE}" ]; then - - CUR_SIZE=$(stat -c%s "${DISK_FILE}") - - if [ "$DATA_SIZE" -gt "$CUR_SIZE" ]; then - - GB=$(( (CUR_SIZE + 1073741823)/1073741824 )) - info "Resizing ${DISK_DESC} from ${GB}G to ${DISK_SPACE} .." + GB=$(( (CUR_SIZE + 1073741823)/1073741824 )) + info "Resizing ${DISK_DESC} from ${GB}G to ${DISK_SPACE} .." + case "${DISK_FMT,,}" in + raw) if [[ "${ALLOCATE}" == [Nn]* ]]; then # Resize file by changing its length @@ -77,55 +102,151 @@ addDisk () { fi fi - fi - fi + ;; + qcow2) + if ! qemu-img resize -f "${DISK_FMT}" "${DISK_FILE}" "${DISK_SPACE}" ; then + error "Could not resize ${DISK_DESC} file (${DISK_FILE}) to ${DISK_SPACE}" && exit 85 + fi + ;; + esac +} - if [ ! -f "${DISK_FILE}" ]; then +convertDisk() { + local CONV_FLAGS="" + local SOURCE_FILE=$1 + local SOURCE_FMT=$2 + local DST_FILE=$3 + local DST_FMT=$4 - if [[ "${ALLOCATE}" == [Nn]* ]]; then + case "${DST_FMT}" in + qcow2) + CONV_FLAGS="${CONV_FLAGS} -c" + esac - # Create an empty file - if ! truncate -s "${DISK_SPACE}" "${DISK_FILE}"; then - rm -f "${DISK_FILE}" - error "Could not create a ${DISK_SPACE} file for ${DISK_DESC} (${DISK_FILE})" && exit 87 - fi - - else + # shellcheck disable=SC2086 + qemu-img convert ${CONV_FLAGS} -f "${SOURCE_FMT}" -O "${DST_FMT}" -- "${SOURCE_FILE}" "${DST_FILE}" +} - # Check free diskspace - SPACE=$(df --output=avail -B 1 "${DIR}" | tail -n 1) - SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) +createDisk() { + local GB + local SPACE + local SPACE_GB + local DISK_FILE=$1 + local DISK_SPACE=$2 + local DISK_DESC=$3 + local DISK_FMT=$4 - if (( DATA_SIZE > SPACE )); then - error "Not enough free space to create ${DISK_DESC} of ${DISK_SPACE} in ${DIR}, it has only ${SPACE_GB} GB available.." - error "Specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation with ALLOCATE=N." && exit 86 - fi + case "${DISK_FMT,,}" in + raw) + if [[ "${ALLOCATE}" == [Nn]* ]]; then - # Create an empty file - if ! fallocate -l "${DISK_SPACE}" "${DISK_FILE}"; then + # Create an empty file if ! truncate -s "${DISK_SPACE}" "${DISK_FILE}"; then rm -f "${DISK_FILE}" error "Could not create a ${DISK_SPACE} file for ${DISK_DESC} (${DISK_FILE})" && exit 87 fi + + else + + # Check free diskspace + SPACE=$(df --output=avail -B 1 "${DIR}" | tail -n 1) + SPACE_GB=$(( (SPACE + 1073741823)/1073741824 )) + + if (( DATA_SIZE > SPACE )); then + error "Not enough free space to create ${DISK_DESC} of ${DISK_SPACE} in ${DIR}, it has only ${SPACE_GB} GB available.." + error "Specify a smaller ${DISK_DESC^^}_SIZE or disable preallocation with ALLOCATE=N." && exit 86 + fi + + # Create an empty file + if ! fallocate -l "${DISK_SPACE}" "${DISK_FILE}"; then + if ! truncate -s "${DISK_SPACE}" "${DISK_FILE}"; then + rm -f "${DISK_FILE}" + error "Could not create a ${DISK_SPACE} file for ${DISK_DESC} (${DISK_FILE})" && exit 87 + fi + fi + + fi + ;; + qcow2) + if ! qemu-img create -f "$DISK_FMT" -- "${DISK_FILE}" "${DISK_SPACE}" ; then + error "Could not create a ${DISK_SPACE} byte ${DISK_FMT} file for ${DISK_DESC} (${DISK_FILE})" && exit 89 + fi + ;; + esac +} + +addDisk () { + + local DIR + local CUR_SIZE + local DATA_SIZE + local DISK_FILE + local DISK_ROOT + local DISK_ID=$1 + local DISK_BASE=$2 + local DISK_EXT=$3 + local DISK_DESC=$4 + local DISK_SPACE=$5 + local DISK_INDEX=$6 + local DISK_ADDRESS=$7 + local DISK_FMT=$8 + + DISK_FILE="${DISK_BASE}.${DISK_EXT}" + + DISK_ROOT="$(basename -- "${DISK_BASE}")" + + DIR=$(dirname "${DISK_FILE}") + [ ! -d "${DIR}" ] && return 0 + + if ! [ -f "${DISK_FILE}" ] ; then + local OTHER_FORMS + OTHER_FORMS="$(find "${DIR}" -maxdepth 1 | sed -n -- "/\/${DISK_ROOT}\./p" | sed -- "/\.${DISK_EXT}$/d")" + + if [[ -n "${OTHER_FORMS}" ]] ; then + local SOURCE_FILE + local SOURCE_EXT + local SOURCE_FMT + SOURCE_FILE="$(echo "${OTHER_FORMS}" | head -n1)" + SOURCE_EXT="$(echo "${SOURCE_FILE//*./}" | sed 's/^.*\.//')" + SOURCE_FMT="$(ext2fmt "${SOURCE_EXT}")" + info "Other disk formats detected for ${DISK_DESC} (${OTHER_FORMS//$'\n'/, }), converting ${SOURCE_FILE}" + if ! convertDisk "${SOURCE_FILE}" "${SOURCE_FMT}" "${DISK_FILE}" "${DISK_FMT}" ; then + info "Disk conversion failed, creating new disk image as fallback" + rm "${DISK_FILE}" fi + fi + fi + + [ -z "$DISK_SPACE" ] && DISK_SPACE="16G" + DISK_SPACE=$(echo "${DISK_SPACE}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') + DATA_SIZE=$(numfmt --from=iec "${DISK_SPACE}") + if [ -f "${DISK_FILE}" ]; then + CUR_SIZE=$(getSize "${DISK_FILE}") + + if [ "$DATA_SIZE" -gt "$CUR_SIZE" ]; then + doResize "${DISK_FILE}" "${CUR_SIZE}" "${DATA_SIZE}" "${DISK_SPACE}" "${DISK_DESC}" "${DISK_FMT}" || exit $? fi + else + createDisk "${DISK_FILE}" "${DISK_SPACE}" "${DISK_DESC}" "${DISK_FMT}" || exit $? fi DISK_OPTS="${DISK_OPTS} \ -device virtio-scsi-pci,id=hw-${DISK_ID},bus=pcie.0,addr=${DISK_ADDRESS} \ - -drive file=${DISK_FILE},if=none,id=drive-${DISK_ID},format=raw,cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \ + -drive file=${DISK_FILE},if=none,id=drive-${DISK_ID},format=${DISK_FMT},cache=${DISK_CACHE},aio=${DISK_IO},discard=${DISK_DISCARD},detect-zeroes=on \ -device scsi-hd,bus=hw-${DISK_ID}.0,channel=0,scsi-id=0,lun=0,drive=drive-${DISK_ID},id=${DISK_ID},rotation_rate=${DISK_ROTATION},bootindex=${DISK_INDEX}" return 0 } -DISK1_FILE="${STORAGE}/data.img" -DISK2_FILE="/storage2/data2.img" -DISK3_FILE="/storage3/data3.img" -DISK4_FILE="/storage4/data4.img" -DISK5_FILE="/storage5/data5.img" -DISK6_FILE="/storage6/data6.img" +DISK_EXT="$(fmt2ext "${DISK_FMT}")" || exit $? + +DISK1_FILE="${STORAGE}/data" +DISK2_FILE="/storage2/data2" +DISK3_FILE="/storage3/data3" +DISK4_FILE="/storage4/data4" +DISK5_FILE="/storage5/data5" +DISK6_FILE="/storage6/data6" : ${DISK2_SIZE:=''} : ${DISK3_SIZE:=''} @@ -133,12 +254,12 @@ DISK6_FILE="/storage6/data6.img" : ${DISK5_SIZE:=''} : ${DISK6_SIZE:=''} -addDisk "userdata" "${DISK1_FILE}" "disk" "${DISK_SIZE}" "1" "0xa" -addDisk "userdata2" "${DISK2_FILE}" "disk2" "${DISK2_SIZE}" "2" "0xb" -addDisk "userdata3" "${DISK3_FILE}" "disk3" "${DISK3_SIZE}" "3" "0xc" -addDisk "userdata4" "${DISK4_FILE}" "disk4" "${DISK4_SIZE}" "4" "0xd" -addDisk "userdata5" "${DISK5_FILE}" "disk5" "${DISK5_SIZE}" "5" "0xe" -addDisk "userdata6" "${DISK6_FILE}" "disk6" "${DISK6_SIZE}" "6" "0xf" +addDisk "userdata" "${DISK1_FILE}" "${DISK_EXT}" "disk" "${DISK_SIZE}" "1" "0xa" "${DISK_FMT}" +addDisk "userdata2" "${DISK2_FILE}" "${DISK_EXT}" "disk2" "${DISK2_SIZE}" "2" "0xb" "${DISK_FMT}" +addDisk "userdata3" "${DISK3_FILE}" "${DISK_EXT}" "disk3" "${DISK3_SIZE}" "3" "0xc" "${DISK_FMT}" +addDisk "userdata4" "${DISK4_FILE}" "${DISK_EXT}" "disk4" "${DISK4_SIZE}" "4" "0xd" "${DISK_FMT}" +addDisk "userdata5" "${DISK5_FILE}" "${DISK_EXT}" "disk5" "${DISK5_SIZE}" "5" "0xe" "${DISK_FMT}" +addDisk "userdata6" "${DISK6_FILE}" "${DISK_EXT}" "disk6" "${DISK6_SIZE}" "6" "0xf" "${DISK_FMT}" addDevice () {