1. #!/bin/sh
  2. # Copyright (c) 2012 Slawomir Wojciech Wojtczak (vermaden)
  3. # All rights reserved.
  4. #
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that following conditions are met:
  7. # 1. Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # 2. Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. #
  13. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 'AS IS' AND ANY
  14. # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16. # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
  17. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  18. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  19. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  20. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  21. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  22. # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. unset LC_ALL
  24. unset LANG
  25. PATH=${PATH}:/bin:/usr/bin:/sbin:/usr/sbin
  26. [ $( uname -r | cut -d '.' -f1 ) -lt 8 ] \
  27. && echo "ERROR: beadm only works on FreeBSD 8.0 or later."
  28. __usage() {
  29. NAME=${0##*/}
  30. echo "usage:"
  31. echo " ${NAME} subcommand cmd_options"
  32. echo
  33. echo " subcommands:"
  34. echo
  35. echo " ${NAME} activate beName"
  36. echo " ${NAME} create [-e nonActiveBe | beName@snapshot] beName"
  37. echo " ${NAME} create beName@snapshot"
  38. echo " ${NAME} destroy beName"
  39. echo " ${NAME} destroy beName@snapshot"
  40. echo " ${NAME} list"
  41. exit 1
  42. }
  43. __be_exist() { # 1=DATASET
  44. zfs list -H -o name ${1} 1> /dev/null 2> /dev/null || {
  45. echo "ERROR: Boot environment '${1##*/}' does not exist"
  46. exit 1
  47. }
  48. }
  49. __be_snapshot() { # 1=DATASET/SNAPSHOT
  50. echo "${1}" | grep -q "@"
  51. }
  52. __be_new() { # 1=SOURCE 2=TARGET
  53. __be_snapshot && {
  54. zfs clone ${1} ${2}
  55. } || {
  56. zfs list -H -o name ${1}@${2##*/} 1> /dev/null 2> /dev/null && {
  57. echo "ERROR: Snapshot '${1}@${2##*/}' exists"
  58. exit 1
  59. }
  60. zfs snapshot -r ${1}@${2##*/} 1> /dev/null 2> /dev/null || {
  61. echo "ERROR: Cannot create snapshot '${1}@${2##*/}'"
  62. exit 1
  63. }
  64. zfs clone ${1}@${2##*/} ${2}
  65. }
  66. BASENAME=${1##*/}
  67. zfs list -H -o name -t filesystem -r ${1} \
  68. | grep -v -E "${1}$" \
  69. | while read I
  70. do
  71. DATASET=$( echo ${I} | sed s/"${POOL}\/ROOT\/${BASENAME}\/"//g )
  72. zfs clone ${I}@${2##*/} ${2}/${DATASET}
  73. done
  74. echo "Created successfully"
  75. }
  76. ROOTFS=$( mount | awk '/ \/ / {print $1}' )
  77. echo ${ROOTFS} | grep -q -E "^/dev/" && {
  78. echo "ERROR: This system does not boot from ZFS pool"
  79. exit 1
  80. }
  81. POOL=$( echo ${ROOTFS} | awk -F '/' '{print $1}' )
  82. [ -f /usr/local/etc/beadm.conf ] && . /usr/local/etc/beadm.conf || BOOTPOOL=${POOL}
  83. [ "${POOL}" = "${BOOTPOOL}" ] && {
  84. BOOTFS=$( zpool list -H -o bootfs ${BOOTPOOL} )
  85. } || {
  86. BOOTFS=` grep -E "^vfs.root.mountfrom=" /boot/loader.conf \
  87. | awk -F '=' '{print $2}' | tr -d '"' | awk -F ':' '{print $2}' `
  88. }
  89. case ${1} in
  90. (list) # --------------------------------------------------------------------
  91. POOL_PREFIX="${POOL}/ROOT"
  92. LIST=$( zfs list -o name,used,mountpoint,creation -s creation -H -d 1 -r ${POOL}/ROOT | grep -E "^${POOL}/ROOT/" )
  93. WIDTH_CREATION=$( echo "${LIST}" | awk '{print $5}' | wc -L )
  94. WIDTH_NAME=$( echo "${LIST}" | awk '{print $1}' | wc -L )
  95. WIDTH_NAME=$(( ${WIDTH_NAME} - ${#POOL_PREFIX} - 1 ))
  96. printf "%-${WIDTH_NAME}s %-6s %-10s %5s %6s %s\n" \
  97. BE Active Mountpoint Space Policy Created
  98. echo "${LIST}" \
  99. | while read NAME USED MOUNTPOINT C R E A T
  100. do
  101. NAME=${NAME##*/}
  102. unset ACTIVE
  103. [ "${POOL_PREFIX}/${NAME}" = "${ROOTFS}" ] && ACTIVE="${ACTIVE}N"
  104. [ "${POOL_PREFIX}/${NAME}" = "${BOOTFS}" ] && ACTIVE="${ACTIVE}R"
  105. [ -z "${ACTIVE}" ] && ACTIVE="-"
  106. printf "%-${WIDTH_NAME}s %-6s " ${NAME} ${ACTIVE}
  107. case ${ACTIVE} in
  108. (N|NR) MOUNT="/" ;;
  109. (*) MOUNT="-" ;;
  110. esac
  111. printf "%-10s %5s %-6s " ${MOUNT} ${USED} "static"
  112. date -j -f "%a %b %d %H:%M %Y" "${C} ${R} ${E} ${A} ${T}" +"%Y-%m-%d %H:%M"
  113. done
  114. ;;
  115. (create) # ------------------------------------------------------------------
  116. case ${#} in
  117. (4)
  118. [ ${2} = "-e" ] || __usage
  119. __be_exist ${POOL}/ROOT/${3}
  120. zfs list -H -o name ${POOL}/ROOT/${4} 2> /dev/null && {
  121. echo "ERROR: Boot environment '${4}' already exists"
  122. exit 1
  123. }
  124. __be_new ${POOL}/ROOT/${3} ${POOL}/ROOT/${4}
  125. ;;
  126. (2)
  127. __be_snapshot ${2} && {
  128. zfs snapshot ${POOL}/ROOT/${2} 2> /dev/null || {
  129. echo "ERROR: Cannot create '${2}' snapshot"
  130. exit 1
  131. }
  132. echo "Created successfully"
  133. } || {
  134. __be_new ${ROOTFS} ${POOL}/ROOT/${2}
  135. }
  136. ;;
  137. (*)
  138. __usage
  139. ;;
  140. esac
  141. ;;
  142. (activate) # ----------------------------------------------------------------
  143. [ "${POOL}" = "${BOOTPOOL}" ] && {
  144. MNT="/tmp/BE"
  145. mkdir -p ${MNT} || {
  146. echo "ERROR: Cannot create '${MNT}' directory"
  147. exit 1
  148. }
  149. zfs set mountpoint=${MNT} ${POOL}/ROOT/${2}
  150. cp /boot/zfs/zpool.cache ${MNT}/boot/zfs/zpool.cache
  151. sed -i '' -E s/"^vfs.root.mountfrom=.*$"/"vfs.root.mountfrom=\"zfs:${POOL}\/ROOT\/${2##*/}\""/g ${MNT}/boot/loader.conf
  152. zfs set mountpoint=legacy ${POOL}/ROOT/${2}
  153. zpool set bootfs=${POOL}/ROOT/${2} ${BOOTPOOL} && {
  154. } || {
  155. echo "ERROR: Failed to activate '${POOL}/ROOT/${2}'"
  156. exit 1
  157. }
  158. } || {
  159. sed -i '' -E s/"^vfs.root.mountfrom=.*$"/"vfs.root.mountfrom=\"zfs:${POOL}\/ROOT\/${2##*/}\""/g /boot/loader.conf
  160. }
  161. zfs list -H -o name -t filesystem -r ${POOL}/ROOT/${2} \
  162. | while read I
  163. do
  164. zfs promote ${POOL}/ROOT/${2} 2> /dev/null
  165. done
  166. echo "Activated successfully"
  167. ;;
  168. (destroy) # ----------------------------------------------------------------
  169. __be_exist ${POOL}/ROOT/${2}
  170. [ "${BOOTFS}" = "${POOL}/ROOT/${2}" ] && {
  171. echo "ERROR: '${POOL}/ROOT/${2}' is current active boot environment"
  172. exit 1
  173. }
  174. echo "Are you sure you want to destroy '${2}'?"
  175. echo -n "This action cannot be undone (y/[n]): "
  176. read CHOICE
  177. case ${CHOICE} in
  178. (Y|y|[Yy][Ee][Ss])
  179. __be_snapshot ${POOL}/ROOT/${2} && {
  180. zfs destroy ${POOL}/ROOT/${2} 1> /dev/null 2> /dev/null || {
  181. echo "ERROR: Snapshot '${2}' is origin for other boot environment(s)"
  182. exit 1
  183. }
  184. echo "Destroyed successfully"
  185. exit 0
  186. } || {
  187. ORIGINS=$( zfs list -r -H -o origin ${POOL}/ROOT/${2} )
  188. zfs destroy ${POOL}/ROOT/${2} 1> /dev/null 2> /dev/null || {
  189. zfs destroy -r ${POOL}/ROOT/${2} 2>&1 \
  190. | grep "${POOL}/ROOT/" \
  191. | grep -v "@" \
  192. | while read I
  193. do
  194. zfs promote ${I} 2> /dev/null
  195. done
  196. echo "${ORIGINS}" \
  197. | while read I
  198. do
  199. zfs destroy -r ${I} 2> /dev/null
  200. done
  201. }
  202. }
  203. echo "Destroyed successfully"
  204. ;;
  205. (*)
  206. echo "'${2}' has not been destroyed"
  207. exit 0
  208. ;;
  209. esac
  210. ;;
  211. (*) # -----------------------------------------------------------------------
  212. __usage
  213. ;;
  214. esac

FreeBSD's beadm early version with support for separate /boot.