1. #!/bin/sh -e
  2. # Copyright (c) 2012 Slawomir Wojciech Wojtczak (vermaden). All rights reserved.
  3. # Copyright (c) 2012 Bryan Drewery (bdrewery). All rights reserved.
  4. # Copyright (c) 2012 Mike Clarke (rawthey). All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that following conditions are met:
  8. # 1. Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # 2. Redistributions in binary form must reproduce the above copyright
  11. # notice, this list of conditions and the following disclaimer in the
  12. # documentation and/or other materials provided with the distribution.
  13. #
  14. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 'AS IS' AND ANY
  15. # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  16. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
  18. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  19. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  20. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  21. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  22. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  23. # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. unset LC_ALL
  25. unset LANG
  26. PATH=${PATH}:/bin:/usr/bin:/sbin:/usr/sbin
  27. if [ $( uname -r | cut -d '.' -f1 ) -lt 8 ]
  28. then
  29. echo "ERROR: beadm works on FreeBSD 8.0 or later"
  30. exit 1
  31. fi
  32. __usage() {
  33. local NAME=${0##*/}
  34. echo "usage:"
  35. echo " ${NAME} activate <beName>"
  36. echo " ${NAME} create [-e nonActiveBe | -e beName@snapshot] <beName>"
  37. echo " ${NAME} create <beName@snapshot>"
  38. echo " ${NAME} destroy [-F] <beName | beName@snapshot>"
  39. echo " ${NAME} list [-a] [-s] [-D] [-H]"
  40. echo " ${NAME} rename <origBeName> <newBeName>"
  41. echo " ${NAME} mount <beName> [mountpoint]"
  42. echo " ${NAME} { umount | unmount } [-f] <beName>"
  43. exit 1
  44. }
  45. # check if boot environment exists
  46. __be_exist() { # 1=DATASET
  47. if ! zfs list -H -o name ${1} 1> /dev/null 2> /dev/null
  48. then
  49. echo "ERROR: Boot environment '${1##*/}' does not exist"
  50. exit 1
  51. fi
  52. }
  53. # check if argument is a snapshot
  54. __be_snapshot() { # 1=DATASET/SNAPSHOT
  55. echo "${1}" | grep -q "@" 2> /dev/null
  56. }
  57. # check if boot environment is mounted
  58. __be_mounted() { # 1=BE
  59. mount 2> /dev/null | grep -q -E "^${1} " 2> /dev/null
  60. }
  61. # check if boot environment is a clone
  62. __be_clone() { # 1=DATASET
  63. if zfs list ${1} 1> /dev/null 2> /dev/null
  64. then
  65. local ORIGIN="$( zfs list -H -o origin ${1} )"
  66. if [ "${ORIGIN}" = "-" ]
  67. then
  68. # boot environment is not a clone
  69. return 1
  70. else
  71. # boot environment is a clone
  72. return 0
  73. fi
  74. else
  75. # boot environment does not exist
  76. return 2
  77. fi
  78. }
  79. # create new boot environment
  80. __be_new() { # 1=SOURCE 2=TARGET
  81. local SOURCE=$( echo ${1} | cut -d '@' -f 1 )
  82. if __be_snapshot ${1}
  83. then
  84. # create boot environment from snapshot
  85. local SNAPSHOT=$( echo ${1} | cut -d '@' -f 2 )
  86. zfs list -r -H -t filesystem -o name ${SOURCE} \
  87. | while read FS
  88. do
  89. if ! zfs list -H -o name ${FS}@${SNAPSHOT} 1> /dev/null 2> /dev/null
  90. then
  91. echo "ERROR: Child snapshot '${FS}@${SNAPSHOT}' does not exist"
  92. exit 1
  93. fi
  94. done
  95. else
  96. # create boot environment from other boot environment
  97. if zfs list -H -o name ${1}@${2##*/} 1> /dev/null 2> /dev/null
  98. then
  99. echo "ERROR: Snapshot '${1}@${2##*/}' already exists"
  100. exit 1
  101. fi
  102. # snapshot format
  103. FMT=$( date "+%Y-%m-%d-%H:%M:%S" )
  104. if ! zfs snapshot -r ${1}@${FMT} 1> /dev/null 2> /dev/null
  105. then
  106. echo "ERROR: Cannot create snapshot '${1}@${FMT}'"
  107. exit 1
  108. fi
  109. fi
  110. # clone properties of source boot environment
  111. zfs list -H -o name -r ${SOURCE} \
  112. | while read FS
  113. do
  114. local OPTS=""
  115. while read NAME PROPERTY VALUE
  116. do
  117. local OPTS="-o ${PROPERTY}=${VALUE} ${OPTS}"
  118. done << EOF
  119. $( zfs get -o name,property,value -s local,received -H all ${FS} | awk '!/[\t ]canmount[\t ]/' )
  120. EOF
  121. DATASET=$( echo ${FS} | awk '{print $1}' | sed -E s/"^${POOL}\/ROOT\/${SOURCE##*/}"/"${POOL}\/ROOT\/${2##*/}"/g )
  122. if [ "${OPTS}" = "-o = " ]
  123. then
  124. local OPTS=""
  125. fi
  126. if __be_snapshot ${1}
  127. then
  128. zfs clone -o canmount=off ${OPTS} ${FS}@${1##*@} ${DATASET}
  129. else
  130. zfs clone -o canmount=off ${OPTS} ${FS}@${FMT} ${DATASET}
  131. fi
  132. done
  133. }
  134. ROOTFS=$( mount | awk '/ \/ / {print $1}' )
  135. if echo ${ROOTFS} | grep -q -m 1 -E "^/dev/"
  136. then
  137. echo "ERROR: This system does not boot from ZFS pool"
  138. exit 1
  139. fi
  140. POOL=$( echo ${ROOTFS} | awk -F '/' '{print $1}' )
  141. if [ -f /usr/local/etc/beadm.conf ]
  142. then
  143. . /usr/local/etc/beadm.conf || BOOTPOOL=${POOL}
  144. fi
  145. if ! zfs list ${POOL}/ROOT 1> /dev/null 2> /dev/null
  146. then
  147. echo "ERROR: This system is not configured for boot environments"
  148. exit 1
  149. fi
  150. if [ "${POOL}" = "${BOOTPOOL}" ]
  151. then
  152. BOOTFS=$( zpool list -H -o bootfs ${BOOTPOOL} )
  153. else
  154. BOOTFS=` grep -E "^vfs.root.mountfrom=" /boot/loader.conf \
  155. | awk -F '=' '{print $2}' | tr -d '"' | awk -F ':' '{print $2}' `
  156. fi
  157. if [ -z "${BOOTFS}" -o "${BOOTFS}" = "-" ]
  158. then
  159. echo "ERROR: ZFS boot pool '${POOL}' has unset 'bootfs' property"
  160. exit 1
  161. fi
  162. case ${1} in
  163. (list) # --------------------------------------------------------------------
  164. OPTION_a=0
  165. OPTION_D=0
  166. OPTION_s=0
  167. shift
  168. while getopts "aDHs" OPT
  169. do
  170. case ${OPT} in
  171. (a) OPTION_a=1 ;;
  172. (D) OPTION_D=1 ;;
  173. (H) OPTION_H=1 ;;
  174. (s) OPTION_s=1
  175. OPTION_a=1 ;;
  176. (*) __usage ;;
  177. esac
  178. done
  179. awk -v POOL="${POOL}" \
  180. -v ROOTFS="${ROOTFS}" \
  181. -v BOOTFS="${BOOTFS}" \
  182. -v OPTION_a="${OPTION_a}" \
  183. -v OPTION_D="${OPTION_D}" \
  184. -v OPTION_H="${OPTION_H}" \
  185. -v OPTION_s="${OPTION_s}" \
  186. 'function __normalize(VALUE) {
  187. if(VALUE == "-" || VALUE == 0)
  188. return 0
  189. else
  190. return substr(VALUE, 1, length(VALUE) - 1) * MULTIPLIER[substr(VALUE, length(VALUE))]
  191. }
  192. function __show_units(VALUE) {
  193. if(VALUE < 1024) { UNIT = "K"; }
  194. else if(VALUE < 1048576) { VALUE /= 1024; UNIT = "M"; }
  195. else if(VALUE < 1073741824) { VALUE /= 1048576; UNIT = "G"; }
  196. else if(VALUE < 1099511627776) { VALUE /= 1073741824; UNIT = "T"; }
  197. else if(VALUE < 1125899906842624) { VALUE /= 1099511627776; UNIT = "P"; }
  198. else if(VALUE < 1152921504606846976) { VALUE /= 1125899906842624; UNIT = "E"; }
  199. else { VALUE /= 1152921504606846976; UNIT = "Z"; }
  200. return sprintf("%.1f%s", VALUE, UNIT)
  201. }
  202. function __get_bename(BENAME) {
  203. sub(BENAME_BEGINS_WITH "\/", "", BENAME)
  204. sub("/.*", "", BENAME)
  205. return BENAME
  206. }
  207. function __convert_date(DATE) {
  208. CMD_DATE = "date -j -f \"%a %b %d %H:%M %Y\" \"" DATE "\" +\"%Y-%m-%d %H:%M\""
  209. CMD_DATE | getline NEW
  210. close(CMD_DATE)
  211. return NEW
  212. }
  213. BEGIN {
  214. BENAME_BEGINS_WITH = POOL "/ROOT"
  215. MULTIPLIER["K"] = 1
  216. MULTIPLIER["M"] = 1024
  217. MULTIPLIER["G"] = 1048576
  218. MULTIPLIER["T"] = 1073741824
  219. MULTIPLIER["P"] = 1099511627776
  220. MULTIPLIER["E"] = 1125899906842624
  221. MULTIPLIER["Z"] = 1152921504606846976
  222. MOUNTPOINT_LENGTH = 10
  223. FSNAME_LENGTH = 2
  224. if(OPTION_a == 1)
  225. FSNAME_LENGTH = 19
  226. CMD_MOUNT="mount"
  227. while(CMD_MOUNT | getline)
  228. if($1 ~ "^" BENAME_BEGINS_WITH)
  229. MOUNTS[$1] = $3
  230. close(CMD_MOUNT)
  231. FS = "\\t"
  232. CMD_ZFS_LIST = "zfs list -H -t all -s creation -o name,used,usedds,usedbysnapshots,usedrefreserv,refer,creation,origin -r "
  233. while(CMD_ZFS_LIST BENAME_BEGINS_WITH | getline) {
  234. if($1 != BENAME_BEGINS_WITH) {
  235. FSNAME = $1
  236. FSNAMES[length(FSNAMES) + 1] = FSNAME
  237. USED = __normalize($2)
  238. USEDBYDATASET = __normalize($3)
  239. USEDBYSNAPSHOTS = __normalize($4)
  240. USEDREFRESERV = __normalize($5)
  241. REFER[FSNAME] = __normalize($6)
  242. CREATIONS[FSNAME] = $7
  243. ORIGINS[FSNAME] = $8
  244. if(FSNAME ~ /@/)
  245. SPACES[FSNAME] = USED
  246. else {
  247. SPACES[FSNAME] = USEDBYDATASET + USEDREFRESERV
  248. if(OPTION_D != 1)
  249. SPACES[FSNAME] += USEDBYSNAPSHOTS
  250. BE = " " __get_bename(FSNAME) " "
  251. if(index(BELIST, BE) == 0)
  252. BELIST = BELIST " " BE
  253. MOUNTPOINT = MOUNTS[FSNAME]
  254. if(MOUNTPOINT) {
  255. if((OPTION_a == 0 && FSNAME == (BENAME_BEGINS_WITH "/" __get_bename(FSNAME))) || (OPTION_a == 1)) {
  256. LM = length(MOUNTPOINT)
  257. if(LM > MOUNTPOINT_LENGTH)
  258. MOUNTPOINT_LENGTH = LM
  259. }
  260. }
  261. else
  262. MOUNTPOINT = "-"
  263. }
  264. if(OPTION_a == 1)
  265. LF = length(FSNAME)
  266. else if(FSNAME !~ /@/)
  267. LF = length(__get_bename(FSNAME))
  268. if(LF > FSNAME_LENGTH)
  269. FSNAME_LENGTH = LF
  270. }
  271. }
  272. close(CMD_ZFS_LIST)
  273. split(BELIST, BENAMES, " ")
  274. if(OPTION_a == 1) {
  275. BE_HEAD = "BE/Dataset/Snapshot"
  276. printf "%-" FSNAME_LENGTH + 2 "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\n", BE_HEAD, "Active", "Mountpoint", "Space", "Created"
  277. }
  278. else if(OPTION_H == 1)
  279. BE_HEAD = ""
  280. else {
  281. BE_HEAD = "BE"
  282. printf "%-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\n", BE_HEAD, "Active", "Mountpoint", "Space", "Created"
  283. }
  284. if(OPTION_s != 1)
  285. SNAPSHOT_FILTER = "(/[^@]*)?$"
  286. for(I = 1; I <= length(BENAMES); I++) {
  287. BENAME = BENAMES[I]
  288. if(OPTION_a == 1) {
  289. printf "\n"
  290. print BENAME
  291. for(J = 1; J <= length(FSNAMES); J++) {
  292. FSNAME = FSNAMES[J]
  293. if(FSNAME ~ "^" BENAME_BEGINS_WITH "/" BENAME SNAPSHOT_FILTER) {
  294. ACTIVE = ""
  295. if(FSNAME == ROOTFS)
  296. ACTIVE = ACTIVE "N"
  297. if(FSNAME == BOOTFS)
  298. ACTIVE = ACTIVE "R"
  299. if(! ACTIVE)
  300. ACTIVE = "-"
  301. MOUNTPOINT = MOUNTS[FSNAME]
  302. if(! MOUNTPOINT)
  303. MOUNTPOINT = "-"
  304. printf " %-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\n", FSNAME, ACTIVE, MOUNTPOINT, __show_units(SPACES[FSNAME]), __convert_date(CREATIONS[FSNAME])
  305. ORIGIN = ORIGINS[FSNAME]
  306. ORIGIN_DISPLAY = ORIGIN
  307. sub(BENAME_BEGINS_WITH "/", "", ORIGIN_DISPLAY)
  308. if(ORIGIN != "-") {
  309. if(OPTION_D == 1)
  310. SPACE = REFER[ORIGIN]
  311. else
  312. SPACE = SPACES[ORIGIN]
  313. printf " %-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\n", " " ORIGIN_DISPLAY, "-", "-", __show_units(SPACE), __convert_date(CREATIONS[ORIGIN])
  314. }
  315. }
  316. }
  317. }
  318. else {
  319. SPACE = 0
  320. ACTIVE = ""
  321. NAME = BENAME_BEGINS_WITH "/" BENAME
  322. if(NAME == ROOTFS)
  323. ACTIVE = ACTIVE "N"
  324. if(NAME == BOOTFS)
  325. ACTIVE = ACTIVE "R"
  326. if(! ACTIVE)
  327. ACTIVE = "-"
  328. for(J = 1; J <= length(FSNAMES); J++) {
  329. FSNAME = FSNAMES[J]
  330. if(FSNAME ~ "^" BENAME_BEGINS_WITH "/" BENAME "(/[^@]*)?$") {
  331. if((BENAME_BEGINS_WITH "/" BENAME) == FSNAME) {
  332. MOUNTPOINT = MOUNTS[FSNAME]
  333. if(! MOUNTPOINT)
  334. MOUNTPOINT = "-"
  335. CREATION = __convert_date(CREATIONS[FSNAME])
  336. }
  337. ORIGIN = ORIGINS[FSNAME]
  338. if(ORIGIN == "-")
  339. SPACE += SPACES[FSNAME]
  340. else {
  341. if(OPTION_D == 1)
  342. SPACE += REFER[FSNAME]
  343. else
  344. SPACE += SPACES[FSNAME] + SPACES[ORIGIN]
  345. }
  346. }
  347. }
  348. if(OPTION_H == 1)
  349. printf "%s\t%s\t%s\t%s\t%s\n", BENAME, ACTIVE, MOUNTPOINT, __show_units(SPACE), CREATION
  350. else
  351. printf "%-" FSNAME_LENGTH "s %-6s %-" MOUNTPOINT_LENGTH "s %6s %s\n", BENAME, ACTIVE, MOUNTPOINT, __show_units(SPACE), CREATION
  352. }
  353. }
  354. }'
  355. ;;
  356. (create) # ------------------------------------------------------------------
  357. case ${#} in
  358. (4)
  359. if ! [ ${2} = "-e" ]
  360. then
  361. __usage
  362. fi
  363. # check if argument for -e option is full path dataset
  364. # argument for -e option must be 'beName' or 'beName@snapshot'
  365. if echo ${3} | grep -q "/" 2> /dev/null
  366. then
  367. __usage
  368. fi
  369. __be_exist ${POOL}/ROOT/${3}
  370. if zfs list -H -o name ${POOL}/ROOT/${4} 1> /dev/null 2> /dev/null
  371. then
  372. echo "ERROR: Boot environment '${4}' already exists"
  373. exit 1
  374. fi
  375. __be_new ${POOL}/ROOT/${3} ${POOL}/ROOT/${4}
  376. ;;
  377. (2)
  378. if __be_snapshot ${2}
  379. then
  380. if ! zfs snapshot -r ${POOL}/ROOT/${2} 1> /dev/null 2> /dev/null
  381. then
  382. echo "ERROR: Cannot create '${2}' recursive snapshot"
  383. exit 1
  384. fi
  385. else
  386. __be_new ${ROOTFS} ${POOL}/ROOT/${2}
  387. fi
  388. ;;
  389. (*)
  390. __usage
  391. ;;
  392. esac
  393. echo "Created successfully"
  394. ;;
  395. (activate) # ----------------------------------------------------------------
  396. if [ ${#} -ne 2 ]
  397. then
  398. __usage
  399. fi
  400. __be_exist ${POOL}/ROOT/${2}
  401. if [ "${BOOTFS}" = "${POOL}/ROOT/${2}" ]
  402. then
  403. echo "Already activated"
  404. exit 0
  405. else
  406. if __be_mounted ${POOL}/ROOT/${2}
  407. then
  408. MNT=$( mount | grep -E "^${POOL}/ROOT/${2} " | awk '{print $3}' )
  409. if [ "${MNT}" != "/" ]
  410. then
  411. # boot environment is not current root and its mounted
  412. echo "ERROR: Boot environment '${2}' is mounted at '${MNT}'"
  413. echo "ERROR: Cannot activate manually mounted boot environment"
  414. exit 1
  415. fi
  416. fi
  417. # do not change root (/) mounted boot environment mountpoint
  418. if [ "${ROOTFS}" != "${POOL}/ROOT/${2}" ]
  419. then
  420. TMPMNT=$( mktemp -d /tmp/tmp.XXXXXX )
  421. if ! mkdir -p ${TMPMNT} 2> /dev/null
  422. then
  423. echo "ERROR: Cannot create '${TMPMNT}' directory"
  424. exit 1
  425. fi
  426. MOUNT=0
  427. while read FS MNT TYPE OPTS DUMP FSCK;
  428. do
  429. if [ "${FS}" = "${POOL}/ROOT/${2}" ]
  430. then
  431. MOUNT=${MNT}
  432. break
  433. fi
  434. done << EOF
  435. $( mount -p )
  436. EOF
  437. if [ ${MOUNT} -eq 0 ]
  438. then
  439. zfs set canmount=noauto ${POOL}/ROOT/${2}
  440. zfs set mountpoint=${TMPMNT} ${POOL}/ROOT/${2}
  441. zfs mount ${POOL}/ROOT/${2}
  442. else
  443. TMPMNT=${MOUNT}
  444. fi
  445. cp /boot/zfs/zpool.cache ${TMPMNT}/boot/zfs/zpool.cache
  446. LOADER_CONFIGS=${TMPMNT}/boot/loader.conf
  447. if [ -f ${TMPMNT}/boot/loader.conf.local ]
  448. then
  449. LOADER_CONFIGS="${LOADER_CONFIGS} ${TMPMNT}/boot/loader.conf.local"
  450. fi
  451. sed -i '' -E s/"^vfs.root.mountfrom=.*$"/"vfs.root.mountfrom=\"zfs:${POOL}\/ROOT\/${2##*/}\""/g ${LOADER_CONFIGS}
  452. if [ ${MOUNT} -eq 0 ]
  453. then
  454. zfs umount ${POOL}/ROOT/${2}
  455. zfs set mountpoint=legacy ${POOL}/ROOT/${2}
  456. fi
  457. fi
  458. if ! zpool set bootfs=${POOL}/ROOT/${2} ${POOL} 1> /dev/null 2> /dev/null
  459. then
  460. echo "ERROR: Failed to activate '${2}' boot environment"
  461. exit 1
  462. fi
  463. fi
  464. # execute ZFS LIST only once
  465. ZFS_LIST=$( zfs list -H -o name -r ${POOL}/ROOT )
  466. # disable automatic mount on all inactive boot environments
  467. echo "${ZFS_LIST}" \
  468. | grep -v "^${POOL}/ROOT/${2}$" \
  469. | grep -v "^${POOL}/ROOT/${2}/" \
  470. | while read NAME
  471. do
  472. zfs set canmount=noauto ${NAME}
  473. done
  474. # enable automatic mount for active boot environment and promote it
  475. echo "${ZFS_LIST}" \
  476. | grep -E "^${POOL}/ROOT/${2}(/|$)" \
  477. | while read NAME
  478. do
  479. zfs set canmount=on ${NAME}
  480. while __be_clone ${NAME}
  481. do
  482. zfs promote ${NAME}
  483. done
  484. done
  485. echo "Activated successfully"
  486. ;;
  487. (destroy) # -----------------------------------------------------------------
  488. if [ "${2}" != "-F" ]
  489. then
  490. DESTROY=${2}
  491. else
  492. DESTROY=${3}
  493. fi
  494. __be_exist ${POOL}/ROOT/${DESTROY}
  495. case ${#} in
  496. (2)
  497. echo "Are you sure you want to destroy '${2}'?"
  498. echo -n "This action cannot be undone (y/[n]): "
  499. read CHOICE
  500. ;;
  501. (3)
  502. if [ "${2}" != "-F" ]
  503. then
  504. __usage
  505. fi
  506. CHOICE=Y
  507. ;;
  508. (*)
  509. __usage
  510. ;;
  511. esac
  512. if [ "${BOOTFS}" = "${POOL}/ROOT/${DESTROY}" ]
  513. then
  514. echo "ERROR: Cannot destroy active boot environment"
  515. exit 1
  516. fi
  517. case ${CHOICE} in
  518. (Y|y|[Yy][Ee][Ss])
  519. # destroy snapshot or boot environment
  520. if __be_snapshot ${POOL}/ROOT/${DESTROY}
  521. then
  522. # destroy desired snapshot
  523. if ! zfs destroy -r ${POOL}/ROOT/${DESTROY} 1> /dev/null 2> /dev/null
  524. then
  525. echo "ERROR: Snapshot '${2}' is origin for other boot environment"
  526. exit 1
  527. fi
  528. else
  529. if __be_clone ${POOL}/ROOT/${DESTROY}
  530. then
  531. # promote clones dependent on snapshots used by destroyed boot environment
  532. zfs list -H -t all -o name,origin \
  533. | while read NAME ORIGIN
  534. do
  535. if echo "${ORIGIN}" | grep -q -E "${POOL}/ROOT/${DESTROY}(/.*@|@)" 2> /dev/null
  536. then
  537. zfs promote ${NAME}
  538. fi
  539. done
  540. # get origins used by destroyed boot environment
  541. ORIGIN_SNAPSHOTS=$( zfs list -H -t all -o origin -r ${POOL}/ROOT/${DESTROY} | grep -v '^-$' | awk -F "@" '{print $2}' | sort -u )
  542. fi
  543. # check if boot environment was created from existing snapshot
  544. ORIGIN=$( zfs list -H -o origin ${POOL}/ROOT/${DESTROY} )
  545. CREATION=$( zfs list -H -o creation ${POOL}/ROOT/${DESTROY} )
  546. CREATION=$( date -j -f "%a %b %d %H:%M %Y" "${CREATION}" +"%Y-%m-%d-%H:%M" )
  547. SNAPSHOT_NAME=$( echo "${ORIGIN}" | cut -d '@' -f 2 | sed -E 's/:[0-9]{2}$//g' )
  548. if [ "${2}" = "-F" ]
  549. then
  550. CHOICE=1
  551. elif [ "${SNAPSHOT_NAME}" != "${CREATION}" ]
  552. then
  553. ORIGIN=$( basename ${ORIGIN} )
  554. echo "Boot environment '${DESTROY}' was created from existing snapshot"
  555. echo -n "Destroy '${ORIGIN}' snapshot? (y/[n]): "
  556. read CHOICE
  557. case ${CHOICE} in
  558. (Y|y|[Yy][Ee][Ss])
  559. CHOICE=1
  560. ;;
  561. (*)
  562. CHOICE=0
  563. echo "Origin snapshot '${ORIGIN}' will be preserved"
  564. ;;
  565. esac
  566. else
  567. CHOICE=1
  568. fi
  569. # destroy boot environment
  570. zfs destroy -r ${POOL}/ROOT/${DESTROY}
  571. # check if boot environment is a clone
  572. if __be_clone ${POOL}/ROOT/${DESTROY}
  573. then
  574. # promote datasets dependent on origins used by destroyed boot environment
  575. ALL_ORIGINS=$( zfs list -H -t all -o name,origin )
  576. echo "${ORIGIN_SNAPSHOTS}" \
  577. | while read S
  578. do
  579. echo "${ALL_ORIGINS}" \
  580. | grep "${S}" \
  581. | awk '{print $1}' \
  582. | while read I
  583. do
  584. zfs promote ${I}
  585. done
  586. done
  587. fi
  588. # destroy origins used by destroyed boot environment
  589. SNAPSHOTS=$( zfs list -H -t snapshot -o name )
  590. echo "${ORIGIN_SNAPSHOTS}" \
  591. | while read S
  592. do
  593. echo "${SNAPSHOTS}" \
  594. | grep "@${S}$" \
  595. | while read I
  596. do
  597. if [ ${CHOICE} -eq 1 ]
  598. then
  599. zfs destroy ${I}
  600. fi
  601. done
  602. done
  603. fi
  604. echo "Destroyed successfully"
  605. ;;
  606. (*)
  607. echo "Boot environment '${DESTROY}' has not been destroyed"
  608. ;;
  609. esac
  610. ;;
  611. (rename) # ------------------------------------------------------------------
  612. if [ ${#} -ne 3 ]
  613. then
  614. __usage
  615. fi
  616. __be_exist ${POOL}/ROOT/${2}
  617. if [ "${BOOTFS}" = "${POOL}/ROOT/${2}" ]
  618. then
  619. echo "ERROR: Renaming active boot environment is not supported"
  620. exit 1
  621. fi
  622. if zfs list -H -o name ${POOL}/ROOT/${3} 2> /dev/null
  623. then
  624. echo "ERROR: Boot environment '${3}' already exists"
  625. exit 1
  626. fi
  627. zfs rename ${POOL}/ROOT/${2} ${POOL}/ROOT/${3}
  628. echo "Renamed successfully"
  629. ;;
  630. (mount) # ------------------------------------------------------------
  631. if [ ${#} -eq 2 ]
  632. then
  633. TARGET=$( mktemp -d /tmp/tmp.XXXXXX )
  634. elif [ ${#} -eq 3 ]
  635. then
  636. TARGET=${3}
  637. else
  638. __usage
  639. fi
  640. __be_exist "${POOL}/ROOT/${2}"
  641. if __be_mounted "${POOL}/ROOT/${2}"
  642. then
  643. MNT=$( mount | grep -E "^${POOL}/ROOT/${2} " | awk '{print $3}' )
  644. echo "Boot environment '${2}' is already mounted at '${MNT}'"
  645. exit 1
  646. fi
  647. if ! mkdir -p ${TARGET} 2> /dev/null
  648. then
  649. echo "ERROR: Cannot create '${TARGET}' mountpoint"
  650. exit 1
  651. fi
  652. if ! mount -t zfs ${POOL}/ROOT/${2} ${TARGET}
  653. then
  654. echo "ERROR: Cannot mount '${2}' at '${TARGET}' mountpoint"
  655. exit 1
  656. fi
  657. PREFIX=$( echo ${POOL}/ROOT/${2}/ | sed 's/\//\\\//g' )
  658. zfs list -H -o name,mountpoint -r ${POOL}/ROOT/${2} \
  659. | grep -v "legacy$" \
  660. | sort -n \
  661. | grep -E "^${POOL}/ROOT/${2}/" \
  662. | while read FS MOUNTPOINT
  663. do
  664. if [ "{FS}" != "${POOL}/ROOT/${2}" ]
  665. then
  666. INHERIT=$( zfs get -H -o source mountpoint ${FS} )
  667. if [ "${INHERIT}" = "local" ]
  668. then
  669. if [ "${MOUNTPOINT}" = "legacy" ]
  670. then
  671. continue
  672. else
  673. MOUNTPOINT="/$( echo "${FS}" | sed s/"${PREFIX}"//g )"
  674. fi
  675. fi
  676. fi
  677. if ! mkdir -p ${TARGET}${MOUNTPOINT} 1> /dev/null 2> /dev/null
  678. then
  679. echo "ERROR: Cannot create '${TARGET}${MOUNTPOINT}' mountpoint"
  680. exit 1
  681. fi
  682. if ! mount -t zfs ${FS} ${TARGET}${MOUNTPOINT} 1> /dev/null 2> /dev/null
  683. then
  684. echo "ERROR: Cannot mount '${FS}' at '${TARGET}${MOUNTPOINT}' mountpoint"
  685. exit 1
  686. fi
  687. done
  688. echo "Mounted successfully on '${TARGET}'"
  689. ;;
  690. (umount|unmount) # ----------------------------------------------------------
  691. if [ ${#} -eq 2 ]
  692. then
  693. # we need this empty section for argument checking
  694. :
  695. elif [ ${#} -eq 3 -a "${2}" = "-f" ]
  696. then
  697. OPTS="-f"
  698. shift
  699. else
  700. __usage
  701. fi
  702. __be_exist "${POOL}/ROOT/${2}"
  703. if ! __be_mounted "${POOL}/ROOT/${2}"
  704. then
  705. echo "Boot environment '${2}' is not mounted"
  706. exit 1
  707. fi
  708. mount \
  709. | awk '{print $1}' \
  710. | grep -E "^${POOL}/ROOT/${2}(/|$)" \
  711. | sort -n -r \
  712. | while read FS
  713. do
  714. if ! umount ${OPTS} ${FS} 1> /dev/null 2> /dev/null
  715. then
  716. echo "ERROR: Cannot umount '${FS}' dataset"
  717. exit 1
  718. fi
  719. done
  720. echo "Unmounted successfully"
  721. ;;
  722. (*) # -----------------------------------------------------------------------
  723. __usage
  724. ;;
  725. esac

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