block-snapshot 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. inode=$(stat -c '%i' "$file")
  24. devnum=$(stat -c '%D' "$file")
  25. if [ -z "$inode" ] || [ -z "$devnum" ]
  26. then
  27. release_lock "block"
  28. fatal "Unable to lookup $file: dev: $devnum inode: $inode"
  29. fi
  30. dev_list=$(losetup -a | grep ' \[0*'${devnum}'\]:'${inode} | cut -d : -f 1)
  31. for loopdev in $dev_list; do
  32. # found existing loop to this file
  33. echo $loopdev
  34. return
  35. done
  36. # assign new loop device
  37. loopdev=$(losetup -f 2>/dev/null || find_free_loopback_dev)
  38. if [ "$loopdev" = '' ]
  39. then
  40. release_lock "block"
  41. fatal 'Failed to find an unused loop device'
  42. fi
  43. do_or_die losetup "$loopdev" "$file"
  44. echo $loopdev
  45. else
  46. test -e "$dev" || fatal "$dev does not exist."
  47. test -b "$dev" || fatal "$dev is not a block device nor file."
  48. fi
  49. }
  50. get_dm_snapshot_name() {
  51. base=$1
  52. cow=$2
  53. echo snapshot-$(stat -c '%D:%i' "$base")-$(stat -c '%D:%i' "$cow")
  54. }
  55. create_dm_snapshot() {
  56. local base_dev cow_dev base_sz
  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
  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=$(xenstore_read_default "$XENBUS_PATH/type" 'MISSING')
  80. case "$command" in
  81. add)
  82. case $t in
  83. snapshot|origin)
  84. p=$(xenstore_read_default "$XENBUS_PATH/params" 'MISSING')
  85. if [ "$p" == "MISSING" ]; then
  86. fatal "Missing device parameters ($t $XENBUS_PATH/params)"
  87. fi
  88. base=${p/:*/}
  89. cow=${p/*:/}
  90. if [ -L "$base" ]; then
  91. base=$(readlink -f "$base") || fatal "$base link does not exist."
  92. fi
  93. if [ -L "$cow" ]; then
  94. cow=$(readlink -f "$cow") || fatal "$cow link does not exist."
  95. fi
  96. # first ensure that snapshot device exists (to write somewhere changes from snapshot-origin)
  97. dm_devname=$(get_dm_snapshot_name "$base" "$cow")
  98. claim_lock "block"
  99. # prepare snapshot device
  100. create_dm_snapshot $dm_devname "$base" "$cow"
  101. if [ "$t" == "snapshot" ]; then
  102. #that's all for snapshot, store name of prepared device
  103. xenstore_write "$XENBUS_PATH/node" "/dev/mapper/$dm_devname"
  104. echo "/dev/mapper/$dm_devname" > "$HOTPLUG_STORE-node"
  105. write_dev /dev/mapper/$dm_devname
  106. elif [ "$t" == "origin" ]; then
  107. # for origin - prepare snapshot-origin device and store its name
  108. dm_devname=origin-$(stat -c '%D:%i' "$base")
  109. create_dm_snapshot_origin $dm_devname "$base"
  110. xenstore_write "$XENBUS_PATH/node" "/dev/mapper/$dm_devname"
  111. echo "/dev/mapper/$dm_devname" > "$HOTPLUG_STORE-node"
  112. write_dev /dev/mapper/$dm_devname
  113. fi
  114. # Save domain name for template commit on device remove
  115. domain=$(xenstore_read_default "$XENBUS_PATH/domain" '')
  116. if [ -z "$domain" ]; then
  117. domid=$(xenstore_read "$XENBUS_PATH/frontend-id")
  118. domain=$(xl domname $domid)
  119. fi
  120. echo $domain > "$HOTPLUG_STORE-domain"
  121. release_lock "block"
  122. exit 0
  123. ;;
  124. esac
  125. ;;
  126. prepare)
  127. t=$2
  128. case $t in
  129. snapshot|origin)
  130. p=$3
  131. base=${p/:*/}
  132. cow=${p/*:/}
  133. if [ -L "$base" ]; then
  134. base=$(readlink -f "$base") || fatal "$base link does not exist."
  135. fi
  136. if [ -L "$cow" ]; then
  137. cow=$(readlink -f "$cow") || fatal "$cow link does not exist."
  138. fi
  139. # first ensure that snapshot device exists (to write somewhere changes from snapshot-origin)
  140. dm_devname=$(get_dm_snapshot_name "$base" "$cow")
  141. claim_lock "block"
  142. # prepare snapshot device
  143. create_dm_snapshot $dm_devname "$base" "$cow"
  144. if [ "$t" == "snapshot" ]; then
  145. #that's all for snapshot, store name of prepared device
  146. echo "/dev/mapper/$dm_devname"
  147. elif [ "$t" == "origin" ]; then
  148. # for origin - prepare snapshot-origin device and store its name
  149. dm_devname=origin-$(stat -c '%D:%i' "$base")
  150. create_dm_snapshot_origin $dm_devname "$base"
  151. echo "/dev/mapper/$dm_devname"
  152. fi
  153. release_lock "block"
  154. exit 0
  155. ;;
  156. esac
  157. ;;
  158. remove|cleanup)
  159. if [ "$command" = "cleanup" ]; then
  160. t=$2
  161. else
  162. t=$(cat $HOTPLUG_STORE-type 2>/dev/null || echo 'MISSING')
  163. fi
  164. case "$t" in
  165. snapshot|origin)
  166. if [ "$command" = "cleanup" ]; then
  167. node=$3
  168. else
  169. node=$(cat "$HOTPLUG_STORE-node" 2> /dev/null)
  170. fi
  171. if [ -z "$node" ]; then
  172. #fatal "No device node to remove"
  173. #Most likely already removed
  174. exit 0
  175. fi
  176. if [ ! -e "$node" ]; then
  177. fatal "Device $node does not exists"
  178. fi
  179. claim_lock "block"
  180. use_count=$(dmsetup info $node|grep Open|awk '{print $3}')
  181. # do not remove snapshot if snapshot origin is still present
  182. if [ "${node/snapshot/}" != "$node" -a -e "/dev/mapper/origin-$(echo $node|cut -d- -f2)" ]; then
  183. use_count=1
  184. fi
  185. if [ "$use_count" -gt 0 ]; then
  186. log info "Device $node still in use - not removing"
  187. release_lock "block"
  188. exit 0
  189. fi
  190. # get list of used (loop) devices
  191. deps="$(dmsetup deps $node | cut -d: -f2 | sed -e 's#(7, \([0-9]\+\))#/dev/loop\1#g')"
  192. # if this is origin
  193. if [ "${node/origin/}" != "$node" ]; then
  194. # remove unused snapshots
  195. for snap in /dev/mapper/snapshot-$(echo $node|cut -d- -f2)-*; do
  196. use_count=$(dmsetup info $snap|grep Open|awk '{print $3}')
  197. if [ "$use_count" -eq 0 ]; then
  198. # unused snapshot - remove it
  199. deps="$deps $(dmsetup deps $snap | cut -d: -f2 | sed -e 's#(7, \([0-9]\+\))#/dev/loop\1#g')"
  200. log debug "Removing $snap"
  201. dmsetup remove $snap
  202. fi
  203. done
  204. if [ "$command" = "remove" ]; then
  205. # Commit template changes
  206. domain=$(cat "$HOTPLUG_STORE-domain")
  207. if [ "$domain" ]; then
  208. # Dont stop on errors
  209. /usr/bin/qvm-template-commit "$domain" || true
  210. fi
  211. fi
  212. fi
  213. if [ -e $node ]; then
  214. log debug "Removing $node"
  215. dmsetup remove $node
  216. fi
  217. # try to free loop devices
  218. for dev in $deps; do
  219. if [ -b "$dev" ]; then
  220. log debug "Removing $dev"
  221. losetup -d $dev 2> /dev/null || true
  222. fi
  223. done
  224. if [ -n "$HOTPLUG_STORE" ]; then
  225. rm $HOTPLUG_STORE-*
  226. fi
  227. release_lock "block"
  228. exit 0
  229. ;;
  230. esac
  231. ;;
  232. esac
  233. # vim:sw=2:et: