123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- #!/bin/bash
- #
- # This Admin API call is implemented as a custom script, instead of dumb
- # passthrough to qubesd because it may get huge amount of data (whole root.img
- # for example). qubesd cannot handle it because:
- # 1. It loads the whole payload into memory, before even start looking at it
- # (and later, do not allow to modify/append it).
- # 2. There is 64kB limit on payload size that qubesd can handle (because of
- # point 1).
- # 3. Performance reasons (qubesd is not optimized for performance, passing
- # such large data stream through it would take ages).
- #
- # The whole admin.vm.volume.Import consists of:
- # 1. Permissions checks, getting a path from appropriate storage pool (done
- # by qubesd)
- # 2. Actual data import (done by this script, using dd)
- # 3. Report final result, produce final response to the caller (done by
- # qubesd)
- #
- # This way we do not pass all the data through qubesd, but still can
- # control the process from there in a meaningful way. Note that the last
- # part (second call to qubesd) may perform all kind of verification (like
- # a signature check on the data, or so) and can also prevent VM from
- # starting (hooking also domain-pre-start event) from not verified image.
- #
- # Note that this script implements two calls:
- # - admin.vm.volume.Import
- # - admin.vm.volume.ImportWithSize
- # In the case of admin.vm.ImportWithSize, the first line of payload is then
- # data size in bytes. This is so that we can already notify qubesd to create a
- # volume with the new size.
- set -e
- # make dd output consistent
- export LC_ALL=C
- # use temporary file, because env variables deal poorly with \0 inside
- tmpfile=$(mktemp)
- trap 'rm -f $tmpfile' EXIT
- requested_size=""
- if [[ "${0##*/}" == admin.vm.volume.ImportWithSize ]]; then
- read -r requested_size
- fi
- echo -n "$requested_size" | qubesd-query -c /var/run/qubesd.internal.sock \
- "$QREXEC_REMOTE_DOMAIN" \
- "internal.vm.volume.ImportBegin" \
- "$QREXEC_REQUESTED_TARGET" \
- "$1" >"$tmpfile"
- # exit if qubesd returned an error (not '0\0')
- if [ "$(head -c 2 "$tmpfile" | xxd -p)" != "3000" ]; then
- cat "$tmpfile"
- exit 1
- fi
- size=$(tail -c +3 "$tmpfile"|cut -d ' ' -f 1)
- path=$(tail -c +3 "$tmpfile"|cut -d ' ' -f 2)
- error=""
- # now process stdin into this path
- if ! sudo dd bs=128K of="$path" count="$size" iflag=count_bytes,fullblock \
- conv=sparse,notrunc,nocreat,fdatasync 2>"$tmpfile"; then
- error="error copying data"
- fi
- # Examine dd's output and check if number of bytes copied matches
- if [ -z "$error" ]; then
- bytes_copied=$(tail -n1 "$tmpfile" | cut -d ' ' -f 1)
- if [ "$bytes_copied" -ne "$size" ]; then
- error="not enough data (copied $bytes_copied bytes, expected $size bytes)"
- fi
- fi
- # Check if there is nothing more to be read from stdin
- if [ -z "$error" ]; then
- if ! dd of="$tmpfile" bs=1 count=1 status=none || \
- [ "$(stat -c %s "$tmpfile")" -ne 0 ]; then
- error="too much data (expected $size bytes)"
- fi
- fi
- if [ -z "$error" ]; then
- status="ok"
- else
- status="fail\n$error"
- fi
- # send status notification to qubesd, and pass its response to the caller
- echo -ne "$status" | qubesd-query -c /var/run/qubesd.internal.sock \
- "$QREXEC_REMOTE_DOMAIN" \
- "internal.vm.volume.ImportEnd" \
- "$QREXEC_REQUESTED_TARGET" \
- "$1"
|