block-snapshot 8.6 KB

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