block-snapshot 8.5 KB

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