block-snapshot 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #!/bin/bash
  2. # Usage: block-snapshot add|remove img-file cow-file
  3. #
  4. # This creates dm-snapshot device on given arguments
  5. dir=$(dirname "$0")
  6. if [ "$1" = "prepare" ] || [ "$1" = "cleanup" ]; then
  7. . "$dir/xen-hotplug-common.sh"
  8. command=$1
  9. else
  10. . "$dir/block-common.sh"
  11. fi
  12. shopt -s nullglob
  13. if [ -n "$XENBUS_PATH" ]; then
  14. HOTPLUG_STORE="/var/run/xen-hotplug/${XENBUS_PATH//\//-}"
  15. fi
  16. get_dev() {
  17. dev=$1
  18. if [ -L "$dev" ]; then
  19. dev=$(readlink -f "$dev") || fatal "$dev link does not exist."
  20. fi
  21. if [ -f "$dev" ]; then
  22. file=$dev
  23. loopdev=$(losetup -j $file | head -1 | cut -d : -f 1)
  24. if [ -n "$loopdev" ]; then
  25. # found existing loop to this file
  26. echo $loopdev
  27. return
  28. fi
  29. # assign new loop device
  30. loopdev=$(losetup -f 2>/dev/null || find_free_loopback_dev)
  31. if [ "$loopdev" = '' ]
  32. then
  33. release_lock "block"
  34. fatal 'Failed to find an unused loop device'
  35. fi
  36. do_or_die losetup "$loopdev" "$file"
  37. echo $loopdev
  38. else
  39. test -e "$dev" || fatal "$dev does not exist."
  40. test -b "$dev" || fatal "$dev is not a block device nor file."
  41. echo "$dev"
  42. fi
  43. }
  44. get_dm_snapshot_name() {
  45. local base cow cow2
  46. base=$1
  47. cow=$2
  48. cow2=$3
  49. name="snapshot-$(stat -c '%D:%i' "$base")-$(stat -c '%D:%i' "$cow")"
  50. if [ -n "$cow2" ]; then
  51. name="$name-$(stat -c '%D:%i' "$cow2")"
  52. fi
  53. echo "$name"
  54. }
  55. create_dm_snapshot() {
  56. local base_dev cow_dev base_sz base cow dm_devname
  57. dm_devname=$1
  58. base=$2
  59. cow=$3
  60. if [ ! -e /dev/mapper/$dm_devname ]; then
  61. # prepare new snapshot device
  62. base_dev=$(get_dev $base)
  63. cow_dev=$(get_dev $cow)
  64. base_sz=$(blockdev --getsz $base_dev)
  65. do_or_die dmsetup create $dm_devname --table "0 $base_sz snapshot $base_dev $cow_dev P 256"
  66. fi
  67. }
  68. create_dm_snapshot_origin() {
  69. local base_dev base_sz dm_devname base
  70. dm_devname=$1
  71. base=$2
  72. if [ ! -e /dev/mapper/$dm_devname ]; then
  73. # prepare new snapshot-origin device
  74. base_dev=$(get_dev $base)
  75. base_sz=$(blockdev --getsz $base_dev)
  76. do_or_die dmsetup create $dm_devname --table "0 $base_sz snapshot-origin $base_dev"
  77. fi
  78. }
  79. t=$(basename $0)
  80. t=${t#block-}
  81. case "$command" in
  82. add)
  83. case $t in
  84. snapshot|origin)
  85. p=$(xenstore_read_default "$XENBUS_PATH/params" 'MISSING')
  86. if [ "$p" == "MISSING" ]; then
  87. fatal "Missing device parameters ($t $XENBUS_PATH/params)"
  88. fi
  89. echo $p > "$HOTPLUG_STORE-params"
  90. echo $t > "$HOTPLUG_STORE-type"
  91. base=${p%%:*}
  92. cow=${p#*:}
  93. cow2=${p##*:}
  94. if [ "$cow" != "$cow2" ]; then
  95. cow=${cow%:*}
  96. else
  97. cow2=""
  98. fi
  99. if [ -L "$base" ]; then
  100. base=$(readlink -f "$base") || fatal "$base link does not exist."
  101. fi
  102. if [ -L "$cow" ]; then
  103. cow=$(readlink -f "$cow") || fatal "$cow link does not exist."
  104. fi
  105. if [ -L "$cow2" ]; then
  106. cow2=$(readlink -f "$cow2") || fatal "$cow2 link does not exist."
  107. fi
  108. # first ensure that snapshot device exists (to write somewhere changes from snapshot-origin)
  109. dm_devname=$(get_dm_snapshot_name "$base" "$cow")
  110. claim_lock "block"
  111. # prepare snapshot device
  112. create_dm_snapshot $dm_devname "$base" "$cow"
  113. if [ -n "$cow2" ]; then
  114. dm_devname_full=$(get_dm_snapshot_name "$base" "$cow" "$cow2")
  115. create_dm_snapshot "$dm_devname_full" "/dev/mapper/$dm_devname" "$cow2"
  116. dm_devname="$dm_devname_full"
  117. fi
  118. if [ "$t" == "snapshot" ]; then
  119. #that's all for snapshot, store name of prepared device
  120. xenstore_write "$XENBUS_PATH/node" "/dev/mapper/$dm_devname"
  121. echo "/dev/mapper/$dm_devname" > "$HOTPLUG_STORE-node"
  122. write_dev /dev/mapper/$dm_devname
  123. elif [ "$t" == "origin" ]; then
  124. # for origin - prepare snapshot-origin device and store its name
  125. dm_devname=origin-$(stat -c '%D:%i' "$base")
  126. create_dm_snapshot_origin $dm_devname "$base"
  127. xenstore_write "$XENBUS_PATH/node" "/dev/mapper/$dm_devname"
  128. echo "/dev/mapper/$dm_devname" > "$HOTPLUG_STORE-node"
  129. write_dev /dev/mapper/$dm_devname
  130. fi
  131. # Save domain name for template commit on device remove
  132. domid=$(xenstore_read "$XENBUS_PATH/frontend-id")
  133. # Store name of target domain in case of stubdom
  134. domid=$(xenstore_read_default "/local/domain/$domid/target" "$domid")
  135. domain=$(xl domname $domid)
  136. echo $domain > "$HOTPLUG_STORE-domain"
  137. release_lock "block"
  138. exit 0
  139. ;;
  140. esac
  141. ;;
  142. prepare)
  143. t=$2
  144. case $t in
  145. snapshot|origin)
  146. p=$3
  147. base=${p%%:*}
  148. cow=${p#*:}
  149. cow2=${p##*:}
  150. if [ "$cow" != "$cow2" ]; then
  151. cow=${cow%:*}
  152. else
  153. cow2=""
  154. fi
  155. if [ -L "$base" ]; then
  156. base=$(readlink -f "$base") || fatal "$base link does not exist."
  157. fi
  158. if [ -L "$cow" ]; then
  159. cow=$(readlink -f "$cow") || fatal "$cow link does not exist."
  160. fi
  161. if [ -L "$cow2" ]; then
  162. cow2=$(readlink -f "$cow2") || fatal "$cow2 link does not exist."
  163. fi
  164. # first ensure that snapshot device exists (to write somewhere changes from snapshot-origin)
  165. dm_devname=$(get_dm_snapshot_name "$base" "$cow")
  166. claim_lock "block"
  167. # prepare snapshot device
  168. create_dm_snapshot $dm_devname "$base" "$cow"
  169. if [ -n "$cow2" ]; then
  170. dm_devname_full=$(get_dm_snapshot_name "$base" "$cow" "$cow2")
  171. create_dm_snapshot "$dm_devname_full" "/dev/mapper/$dm_devname" "$cow2"
  172. dm_devname="$dm_devname_full"
  173. fi
  174. if [ "$t" == "snapshot" ]; then
  175. #that's all for snapshot, store name of prepared device
  176. echo "/dev/mapper/$dm_devname"
  177. elif [ "$t" == "origin" ]; then
  178. # for origin - prepare snapshot-origin device and store its name
  179. dm_devname=origin-$(stat -c '%D:%i' "$base")
  180. create_dm_snapshot_origin $dm_devname "$base"
  181. echo "/dev/mapper/$dm_devname"
  182. fi
  183. release_lock "block"
  184. exit 0
  185. ;;
  186. esac
  187. ;;
  188. remove|cleanup)
  189. if [ "$command" = "cleanup" ]; then
  190. t=$2
  191. else
  192. t=$(cat $HOTPLUG_STORE-type 2>/dev/null || echo 'MISSING')
  193. fi
  194. case "$t" in
  195. snapshot|origin)
  196. if [ "$command" = "cleanup" ]; then
  197. node=$3
  198. else
  199. node=$(cat "$HOTPLUG_STORE-node" 2> /dev/null)
  200. fi
  201. if [ -z "$node" ]; then
  202. #fatal "No device node to remove"
  203. #Most likely already removed
  204. exit 0
  205. fi
  206. if [ ! -e "$node" ]; then
  207. fatal "Device $node does not exists"
  208. fi
  209. claim_lock "block"
  210. use_count=$(dmsetup info $node 2>/dev/null|grep Open|awk '{print $3}')
  211. if [ -z "$use_count" ]; then
  212. log info "Device $node already removed"
  213. release_lock "block"
  214. exit 0
  215. fi
  216. # do not remove snapshot if snapshot origin is still present
  217. if [ "${node/snapshot/}" != "$node" -a -e "/dev/mapper/origin-$(echo $node|cut -d- -f2)" ]; then
  218. use_count=1
  219. fi
  220. if [ "$use_count" -gt 0 ]; then
  221. log info "Device $node still in use - not removing"
  222. release_lock "block"
  223. exit 0
  224. fi
  225. # get list of used (loop) devices
  226. deps="$(dmsetup deps $node -o blkdevname | cut -d: -f2 | sed -e 's#(\([a-z0-9-]\+\))#/dev/\1#g')"
  227. # if this is origin
  228. if [ "${node/origin/}" != "$node" ]; then
  229. # remove unused snapshots
  230. for snap in /dev/mapper/snapshot-$(echo $node|cut -d- -f2)-*; do
  231. use_count=$(dmsetup info $snap|grep Open|awk '{print $3}')
  232. if [ "$use_count" -eq 0 ]; then
  233. # unused snapshot - remove it
  234. deps="$deps $(dmsetup deps $snap -o blkdevname | cut -d: -f2 |\
  235. sed -e 's#(\([a-z0-9-]\+\))#/dev/\1#g')"
  236. log debug "Removing $snap"
  237. dmsetup remove $snap
  238. fi
  239. done
  240. if [ "$command" = "remove" ]; then
  241. # Commit template changes
  242. domain=$(cat "$HOTPLUG_STORE-domain")
  243. if [ "$domain" ]; then
  244. if [ -r /var/lib/qubes/qubes-test.xml -a \
  245. "${domain#test-}" != "$domain" ]; then
  246. export QUBES_XML_PATH=/var/lib/qubes/qubes-test.xml
  247. fi
  248. # Dont stop on errors
  249. /usr/bin/qvm-template-commit --offline-mode "$domain" || true
  250. fi
  251. fi
  252. fi
  253. if [ -e $node ]; then
  254. log debug "Removing $node"
  255. dmsetup remove $node
  256. fi
  257. # try to free unused devices
  258. for dev in $deps; do
  259. if [ -b "$dev" ]; then
  260. log debug "Removing $dev"
  261. case $dev in
  262. /dev/loop*)
  263. losetup -d $dev 2> /dev/null || true
  264. ;;
  265. /dev/dm-*)
  266. dmsetup remove $dev 2> /dev/null || true
  267. ;;
  268. esac
  269. fi
  270. done
  271. if [ -n "$HOTPLUG_STORE" ]; then
  272. rm $HOTPLUG_STORE-*
  273. fi
  274. release_lock "block"
  275. exit 0
  276. ;;
  277. esac
  278. ;;
  279. esac
  280. # vim:sw=2:et: