admin.vm.volume.Import 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. #!/bin/bash
  2. #
  3. # This Admin API call is implemented as a custom script, instead of dumb
  4. # passthrough to qubesd because it may get huge amount of data (whole root.img
  5. # for example). qubesd cannot handle it because:
  6. # 1. It loads the whole payload into memory, before even start looking at it
  7. # (and later, do not allow to modify/append it).
  8. # 2. There is 64kB limit on payload size that qubesd can handle (because of
  9. # point 1).
  10. # 3. Performance reasons (qubesd is not optimized for performance, passing
  11. # such large data stream through it would take ages).
  12. #
  13. # The whole admin.vm.volume.Import consists of:
  14. # 1. Permissions checks, getting a path from appropriate storage pool (done
  15. # by qubesd)
  16. # 2. Actual data import (done by this script, using dd)
  17. # 3. Report final result, produce final response to the caller (done by
  18. # qubesd)
  19. #
  20. # This way we do not pass all the data through qubesd, but still can
  21. # control the process from there in a meaningful way. Note that the last
  22. # part (second call to qubesd) may perform all kind of verification (like
  23. # a signature check on the data, or so) and can also prevent VM from
  24. # starting (hooking also domain-pre-start event) from not verified image.
  25. #
  26. # Note that this script implements two calls:
  27. # - admin.vm.volume.Import
  28. # - admin.vm.volume.ImportWithSize
  29. # In the case of admin.vm.ImportWithSize, the first line of payload is then
  30. # data size in bytes. This is so that we can already notify qubesd to create a
  31. # volume with the new size.
  32. set -e
  33. # make dd output consistent
  34. export LC_ALL=C
  35. # use temporary file, because env variables deal poorly with \0 inside
  36. tmpfile=$(mktemp)
  37. trap 'rm -f $tmpfile' EXIT
  38. requested_size=""
  39. if [[ "${0##*/}" == admin.vm.volume.ImportWithSize ]]; then
  40. read -r requested_size
  41. fi
  42. echo -n "$requested_size" | qubesd-query -c /var/run/qubesd.internal.sock \
  43. "$QREXEC_REMOTE_DOMAIN" \
  44. "internal.vm.volume.ImportBegin" \
  45. "$QREXEC_REQUESTED_TARGET" \
  46. "$1" >"$tmpfile"
  47. # exit if qubesd returned an error (not '0\0')
  48. if [ "$(head -c 2 "$tmpfile" | xxd -p)" != "3000" ]; then
  49. cat "$tmpfile"
  50. exit 1
  51. fi
  52. size=$(tail -c +3 "$tmpfile"|cut -d ' ' -f 1)
  53. path=$(tail -c +3 "$tmpfile"|cut -d ' ' -f 2)
  54. error=""
  55. # now process stdin into this path
  56. if ! sudo dd bs=128K of="$path" count="$size" iflag=count_bytes,fullblock \
  57. conv=sparse,notrunc,nocreat,fdatasync 2>"$tmpfile"; then
  58. error="error copying data"
  59. fi
  60. # Examine dd's output and check if number of bytes copied matches
  61. if [ -z "$error" ]; then
  62. bytes_copied=$(tail -n1 "$tmpfile" | cut -d ' ' -f 1)
  63. if [ "$bytes_copied" -ne "$size" ]; then
  64. error="not enough data (copied $bytes_copied bytes, expected $size bytes)"
  65. fi
  66. fi
  67. # Check if there is nothing more to be read from stdin
  68. if [ -z "$error" ]; then
  69. if ! dd of="$tmpfile" bs=1 count=1 status=none || \
  70. [ "$(stat -c %s "$tmpfile")" -ne 0 ]; then
  71. error="too much data (expected $size bytes)"
  72. fi
  73. fi
  74. if [ -z "$error" ]; then
  75. status="ok"
  76. else
  77. status="fail\n$error"
  78. fi
  79. # send status notification to qubesd, and pass its response to the caller
  80. echo -ne "$status" | qubesd-query -c /var/run/qubesd.internal.sock \
  81. "$QREXEC_REMOTE_DOMAIN" \
  82. "internal.vm.volume.ImportEnd" \
  83. "$QREXEC_REQUESTED_TARGET" \
  84. "$1"