## $Id: lvm-snaptool.lib.sh,v 1.5 2009/05/27 15:39:33 wschlich Exp wschlich $ ## vim:ts=4:sw=4:nu:ai:nowrap: ## ## Created by Wolfram Schlich ## Licensed under the GNU GPLv3 ## ## ## NOTES ## ===== ## - with XFS, do NOT manually call xfs_freeze: ## http://readlist.com/lists/redhat.com/linux-lvm/0/952.html ## http://readlist.com/lists/redhat.com/linux-lvm/0/957.html ## http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=285979 ## ## ## REQUIRED PROGRAMS IN PATH ## ========================= ## - bc ## - cut ## - env ## - sort ## - touch ## - rm ## - mkdir ## - mount ## - umount ## - readlink ## - mktemp ## - blkid ## - xargs ## - hostname ## - uname ## - lvs ## - lvcreate ## - lvremove ## ## ## application control functions ## function __init() { ## parse command line options while getopts ':CDMd:e:f:hqs:' opt; do case "${opt}" in ## create snapshots C) Task="create" ;; ## delete snapshots D) Task="delete" ;; ## mirror complete system M) Mode="mirror" ;; ## snapshot volume mount directory d) SnapshotVolumeMountDirectory="${OPTARG}" ;; ## exclude specific LVs e) LogicalVolumeExcludeList="${OPTARG}" ;; ## snapshot volume size factor f) SnapshotVolumeSizeFactor="${OPTARG}" ;; ## help h) Task="help" ;; ## quiet operation q) __MsgQuiet=1 ;; ## snapshot volume name suffix s) SnapshotVolumeNameSuffix="${OPTARG}" ;; ## option without a required argument :) __die 2 "option -${OPTARG} requires an argument" # TODO FIXME: switch to __msg err ;; ## unknown option \?) __die 2 "unknown option -${OPTARG}" # TODO FIXME: switch to __msg err ;; ## this should never happen *) __die 2 "there's an error in the matrix!" # TODO FIXME: switch to __msg err ;; esac __msg debug "command line argument: -${opt}${OPTARG:+ '${OPTARG}'}" done ## check if command line options were given at all if [[ ${OPTIND} -eq 1 ]]; then __msg err "no command line option specified" printUsage && exit 2 fi ## shift off options + arguments let OPTIND--; shift ${OPTIND}; unset OPTIND args="${@}" set -- ## just show help? if [[ ${Task} == "help" ]]; then printUsage && exit 0 fi ## populate logical volume array ## from logical volume list IFS=',' LogicalVolumeArray=( ${LogicalVolumeList} ) unset IFS #validateLogicalVolumeArray # TODO FIXME: implement function ##*/*) # "any/any" ## populate logical volume exclude array ## from logical volume exclude list IFS=',' LogicalVolumeExcludeArray=( ${LogicalVolumeExcludeList} ) unset IFS #validateLogicalVolumeExcludeArray # TODO FIXME: implement function ## populate snapshot volume size factor array ## from snapshot volume size factor list IFS=',' SnapshotVolumeSizeFactorArray=( ${SnapshotVolumeSizeFactor} ) unset IFS #validateSnapshotVolumeSizeFactorArray # TODO FIXME: implement function ##*/*:+([0-9])|*/*:+([0-9].[0-9])) # "any/any:num" or "any/any:num.num" ##+([0-9])|+([0-9].[0-9])) # "num" or "num.num" ## populate snapshot volume mount directory array ## from snapshot volume mount directory list IFS=',' SnapshotVolumeMountDirectoryArray=( ${SnapshotVolumeMountDirectory} ) unset IFS #validateSnapshotVolumeMountDirectoryArray # TODO FIXME: implement function ## system mirror mode case ${Mode} in mirror) case ${Task} in create) ## check arguments for mirror creation if [[ -z ${SnapshotVolumeMountDirectory} ]]; then __msg err "snapshot volume mount directory not specified" printUsage && exit 2 fi ;; delete) ## check arguments for mirror deletion if [[ -z ${SnapshotVolumeMountDirectory} ]]; then __msg err "snapshot volume mount directory not specified" printUsage && exit 2 fi ;; '') __msg err "mode '${Mode}': no task specified" printUsage && exit 2 ;; *) __msg err "mode '${Mode}': invalid task specified: '${Task}'" printUsage && exit 2 ;; esac ;; '') __msg err "no mode specified" printUsage && exit 2 ;; *) __msg err "invalid mode specified: '${Mode}'" printUsage && exit 2 ;; esac } # __init() function __main() { if [[ ${Mode} == "mirror" ]]; then case ${Task} in create) ## create mirror if ! createSystemMirror; then __die 2 "failed to create system mirror" else __msg info "successfully created system mirror" fi ;; delete) ## delete mirror if ! deleteSystemMirror; then __die 2 "failed to delete system mirror" else __msg info "successfully deleted system mirror" fi ;; esac fi } # __main() ## ## application worker functions ## function printUsage() { ## ----- head ----- ## ## DESCRIPTION: ## prints usage information ## ## ARGUMENTS: ## / ## ## GLOBAL VARIABLES USED: ## / ## ## ----- main ----- cat <<-USAGE Usage: ${__ScriptFile} [options] Options: -M: system mirror mode, creates a mirror of all currently mounted volumes in a new directory structure. requires: one of -C -D accepts: -d -e -f -C: create snapshot(s). requires: -M excludes: -D accepts: -d -e -f -s -D: delete snapshot(s). requires: -M excludes: -C accepts: -d -e -s ignores: -f -e arg: logical volumes to exclude. example: -e vg.sys/lv.home -e vg.sys/lv.home,vg.sys/lv.usr -f arg: snapshot volume size factor. example: -f 0.2 -f vg.sys/lv.home:0.1,vg.sys/lv.var:0.2,0.5 default: ${SnapshotVolumeSizeFactor} -s arg: snapshot volume name suffix. example: -s .snapshot default: ${SnapshotVolumeNameSuffix} -d arg: mount directory for system mirror. example: -d /mnt/lvm-snapshots default: ${SnapshotVolumeMountDirectory} Options -M and one of -C -D are required. USAGE return 0 # success } function createSnapshot() { ## ----- head ----- ## ## DESCRIPTION: ## creates a snapshot of a logical volume. ## ## ARGUMENTS: ## 1: volumeGroupName (req): vg.sys ## 2: logicalVolumeName (req): lv.home ## 3: snapshotVolumeName (req): lv.home.snapshot ## 4: snapshotVolumeSizeFactor (req): 0.2 (=20%) ## ## GLOBAL VARIABLES USED: ## LvmDevicesDirectory ## local volumeGroupName=${1} if [[ -z "${volumeGroupName}" ]]; then __msg err "argument 1 (volumeGroupName) missing" return 2 # error fi __msg debug "volumeGroupName: ${volumeGroupName}" local logicalVolumeName=${2} if [[ -z "${logicalVolumeName}" ]]; then __msg err "argument 2 (logicalVolumeName) missing" return 2 # error fi __msg debug "logicalVolumeName: ${logicalVolumeName}" local snapshotVolumeName=${3} if [[ -z "${snapshotVolumeName}" ]]; then __msg err "argument 3 (snapshotVolumeName) missing" return 2 # error fi __msg debug "snapshotVolumeName: ${snapshotVolumeName}" local snapshotVolumeSizeFactor=${4} if [[ -z "${snapshotVolumeSizeFactor}" ]]; then __msg err "argument 4 (snapshotVolumeSizeFactor) missing" return 2 # error fi __msg debug "snapshotVolumeSizeFactor: ${snapshotVolumeSizeFactor}" ## ----- main ----- ## generate logical volume device (e.g. /dev/vg.sys/lv.home) local logicalVolumeDevice="${LvmDevicesDirectory:-/dev}/${volumeGroupName}/${logicalVolumeName}" __msg debug "logicalVolumeDevice: ${logicalVolumeDevice}" ## check if device is a logical volume if ! deviceIsLogicalVolume "${logicalVolumeDevice}"; then __msg err "device '${logicalVolumeDevice}' is not a logical volume" return 2 # error fi ## get logical volume size local logicalVolumeSize=$(env LVM_SUPPRESS_FD_WARNINGS=1 lvs --separator : --noheadings --nosuffix \ --units m -o lv_name,lv_size "${logicalVolumeDevice}" 2>>"${_L}" | cut -d : -f 2 2>>"${_L}") local -i lvsExitCode=${PIPESTATUS[0]} local -i cutExitCode=${PIPESTATUS[1]} if [[ ${lvsExitCode} -ne 0 || ${cutExitCode} -ne 0 || -z "${logicalVolumeSize}" ]]; then __msg err "failed to get size of logical volume '${logicalVolumeDevice}'" return 2 # error fi __msg debug "logicalVolumeSize: ${logicalVolumeSize}" ## calculate snapshot volume size local snapshotVolumeSize=$(echo "${logicalVolumeSize} * ${snapshotVolumeSizeFactor}" | bc) local -i bcExitCode=${PIPESTATUS[1]} if [[ ${bcExitCode} -ne 0 || -z "${snapshotVolumeSize}" ]]; then __msg err "failed to calculate snapshot volume size for logical volume '${logicalVolumeDevice}'" return 2 # error fi __msg debug "snapshotVolumeSize: ${snapshotVolumeSize}" ## generate snapshot volume device (e.g. /dev/vg.sys/lv.home.snapshot) local snapshotVolumeDevice="${LvmDevicesDirectory:-/dev}/${volumeGroupName}/${snapshotVolumeName}" __msg debug "snapshotVolumeDevice: ${snapshotVolumeDevice}" ## check if snapshot volume device already exists if [[ -e "${snapshotVolumeDevice}" ]]; then __msg err "snapshot volume device '${snapshotVolumeDevice}' already exists" return 2 # error fi ## create snapshot volume if ! env LVM_SUPPRESS_FD_WARNINGS=1 lvcreate -s -p r -c 512 -C y --addtag snapshots \ -L "${snapshotVolumeSize}m" -n "${snapshotVolumeName}" \ "${logicalVolumeDevice}" >>"${_L}" 2>&1; then __msg err "failed to create snapshot volume '${snapshotVolumeName}' of" \ "logical volume '${volumeGroupName}/${logicalVolumeName}'" return 2 # error else __msg debug "successfully created snapshot volume '${snapshotVolumeName}' of" \ "logical volume '${volumeGroupName}/${logicalVolumeName}'" fi return 0 # success } # createSnapshot() function deleteSnapshot() { ## ----- head ----- ## ## DESCRIPTION: ## deletes a snapshot volume. ## ## ARGUMENTS: ## 1: volumeGroupName (req): vg.sys ## 2: snapshotVolumeName (req): lv.home.snapshot ## ## GLOBAL VARIABLES USED: ## LvmDevicesDirectory ## local volumeGroupName=${1} if [[ -z "${volumeGroupName}" ]]; then __msg err "argument 1 (volumeGroupName) missing" return 2 # error fi __msg debug "volumeGroupName: ${volumeGroupName}" local snapshotVolumeName=${2} if [[ -z "${snapshotVolumeName}" ]]; then __msg err "argument 2 (snapshotVolumeName) missing" return 2 # error fi __msg debug "snapshotVolumeName: ${snapshotVolumeName}" ## ----- main ----- ## generate snapshot volume device (e.g. /dev/vg.sys/lv.home.snapshot) local snapshotVolumeDevice="${LvmDevicesDirectory:-/dev}/${volumeGroupName}/${snapshotVolumeName}" __msg debug "snapshotVolumeDevice: ${snapshotVolumeDevice}" ## check if device is a snapshot volume if ! deviceIsSnapshotVolume "${snapshotVolumeDevice}"; then __msg err "device '${snapshotVolumeDevice}' is not a snapshot volume" return 2 # error fi ## remove snapshot volume if ! env LVM_SUPPRESS_FD_WARNINGS=1 lvremove -f "${snapshotVolumeDevice}" >>"${_L}" 2>&1; then __msg err "failed to remove snapshot volume device '${snapshotVolumeDevice}'" return 2 # error else __msg debug "successfully removed snapshot volume device '${snapshotVolumeDevice}'" fi return 0 # success } # deleteSnapshot() function mountSnapshot() { ## ----- head ----- ## ## DESCRIPTION: ## mounts a snapshot volume on a directory. ## ## ARGUMENTS: ## 1: volumeGroupName (req): vg.sys ## 2: snapshotVolumeName (req): lv.home ## 3: snapshotVolumeMountDirectory (req): /mnt/snapshots/home ## ## GLOBAL VARIABLES USED: ## LvmDevicesDirectory (fallback: /dev) ## local volumeGroupName=${1} if [[ -z "${volumeGroupName}" ]]; then __msg err "argument 1 (volumeGroupName) missing" return 2 # error fi __msg debug "volumeGroupName: ${volumeGroupName}" local snapshotVolumeName=${2} if [[ -z "${snapshotVolumeName}" ]]; then __msg err "argument 2 (snapshotVolumeName) missing" return 2 # error fi __msg debug "snapshotVolumeName: ${snapshotVolumeName}" local snapshotVolumeMountDirectory=${3} if [[ -z "${snapshotVolumeMountDirectory}" ]]; then __msg err "argument 3 (snapshotVolumeMountDirectory) missing" return 2 # error fi __msg debug "snapshotVolumeMountDirectory: ${snapshotVolumeMountDirectory}" ## ----- main ----- ## generate snapshot volume device (e.g. /dev/vg.sys/lv.home) local snapshotVolumeDevice="${LvmDevicesDirectory:-/dev}/${volumeGroupName}/${snapshotVolumeName}" __msg debug "snapshotVolumeDevice: ${snapshotVolumeDevice}" ## check if snapshot volume device exists if [[ ! -e "${snapshotVolumeDevice}" ]]; then __msg err "snapshot volume device '${snapshotVolumeDevice}' does not exist" return 2 # error fi ## check if device is a snapshot volume if ! deviceIsSnapshotVolume "${snapshotVolumeDevice}"; then __msg err "device '${snapshotVolumeDevice}' is not a snapshot volume" return 2 # error fi ## use mount options based on determined filesystem type local filesystemType=$(blkid -c /dev/null -s TYPE -o value ${snapshotVolumeDevice} 2>>"${_L}") local -i blkidExitCode=${?} if [[ ${blkidExitCode} -ne 0 ]]; then __msg err "failed running blkid to determine filesystem type of snapshot volume device '${snapshotVolumeDevice}'" return 2 # error fi __msg debug "filesystemType: ${filesystemType}" local mountOpts= case "${filesystemType}" in xfs) ## special mount options for XFS mountOpts="nouuid,norecovery,ro" ;; *) ## default mount options mountOpts="ro" ;; esac __msg debug "mountOpts: ${mountOpts}" ## check for mount directory if [[ ! -d "${snapshotVolumeMountDirectory}" ]]; then __msg info "snapshot volume mount directory '${snapshotVolumeMountDirectory}'" \ "doesn't exist -- creating..." if ! mkdir -p "${snapshotVolumeMountDirectory}"; then __msg err "failed to create snapshot volume mount directory" \ "'${snapshotVolumeMountDirectory}'" return 2 # error else __msg debug "successfully created snapshot volume mount directory" \ "'${snapshotVolumeMountDirectory}'" fi fi ## mount snapshot volume if ! mount -o ${mountOpts} "${snapshotVolumeDevice}" "${snapshotVolumeMountDirectory}"; then __msg err "failed to mount snapshot volume device '${snapshotVolumeDevice}'" \ "on '${snapshotVolumeMountDirectory}' with options '${mountOpts}'" return 2 # error else __msg debug "successfully mounted snapshot volume device '${snapshotVolumeDevice}'" \ "on '${snapshotVolumeMountDirectory}' with options '${mountOpts}" fi return 0 # success } # mountSnapshot() function bindMountDirectory() { ## ----- head ----- ## ## DESCRIPTION: ## bind-mounts a directory on another. ## ## ARGUMENTS: ## 1: sourceDirectory (req) ## 2: targetDirectory (req) ## ## GLOBAL VARIABLES USED: ## / ## local sourceDirectory=${1} if [[ -z "${sourceDirectory}" ]]; then __msg err "argument 1 (sourceDirectory) missing" return 2 # error fi __msg debug "sourceDirectory: ${sourceDirectory}" local targetDirectory=${2} if [[ -z "${targetDirectory}" ]]; then __msg err "argument 2 (targetDirectory) missing" return 2 # error fi __msg debug "targetDirectory: ${targetDirectory}" ## ----- main ----- ## check for source directory if [[ ! -d "${sourceDirectory}" ]]; then __msg err "source directory '${sourceDirectory}' doesn't exist" return 2 # error fi ## check for target directory if [[ ! -d "${targetDirectory}" ]]; then __msg info "target directory '${targetDirectory}' doesn't exist -- creating..." if ! mkdir -p "${targetDirectory}"; then __msg err "failed to create target directory '${targetDirectory}'" return 2 # error else __msg debug "successfully created target directory '${targetDirectory}'" fi fi ## check if mount point directory actually has something mounted on if directoryHasMount "${targetDirectory}"; then __msg err "something is already mounted on directory '${targetDirectory}'" return 2 # error fi ## bind-mount source directory on target directory if ! mount -o bind "${sourceDirectory}" "${targetDirectory}" >>"${_L}" 2>&1; then __msg err "failed to bind-mount directory '${sourceDirectory}' on directory '${targetDirectory}'" return 2 # error else __msg debug "successfully bind-mounted directory '${sourceDirectory}' on directory '${targetDirectory}'" fi ## remount bind-mounted directory ro (supported from kernel 2.6.26 on), ## see http://lwn.net/Articles/281157/ local kernelRelease=$(uname -r) local kernelRelease=${kernelRelease/[^0-9.]*/} # strip localversion IFS='.' set -- ${kernelRelease} unset IFS local -i kernelVersionMajor=${1} local -i kernelVersionMinor=${2} local -i kernelVersionPatchlevel=${3} set -- if [[ ${kernelVersionMajor} -ge 2 && ${kernelVersionMinor} -ge 6 && ${kernelVersionPatchlevel} -ge 26 ]]; then __msg debug "trying to remount bind-mounted directory read-only (kernel version >= 2.6.26)" if ! mount -o remount,ro "${targetDirectory}" >>"${_L}" 2>&1; then __msg err "failed to remount bind-mounted directory '${targetDirectory}' read-only" return 2 # error else __msg debug "successfully remounted bind-mounted directory '${targetDirectory}' read-only" fi else __msg debug "not trying to remount bind-mounted directory read-only (kernel version < 2.6.26)" fi return 0 # success } # bindMountDirectory() function unmount() { ## ----- head ----- ## ## DESCRIPTION: ## unmounts a filesystem from a directory. ## ## ARGUMENTS: ## 1: mountPointDirectory (req) ## ## GLOBAL VARIABLES USED: ## / ## local mountPointDirectory=${1} if [[ -z "${mountPointDirectory}" ]]; then __msg err "argument 1 (mountPointDirectory) missing" return 2 # error fi __msg debug "mountPointDirectory: ${mountPointDirectory}" ## ----- main ----- ## check if mount point directory actually has something mounted on if ! directoryHasMount "${mountPointDirectory}"; then __msg err "nothing mounted on directory '${mountPointDirectory}'" return 2 # error fi ## unmount filesystem from mount point directory if ! umount "${mountPointDirectory}" >>"${_L}" 2>&1; then __msg err "failed to unmount filesystem from directory '${mountPointDirectory}'" return 2 # error else __msg debug "successfully unmounted filesystem from directory '${mountPointDirectory}'" fi return 0 # success } # unmount() function deviceIsLogicalVolume() { ## ----- head ----- ## ## DESCRIPTION: ## checks if the given device is a logical volume. ## ## ARGUMENTS: ## 1: device (req) ## ## GLOBAL VARIABLES USED: ## / ## local device=${1} if [[ -z "${device}" ]]; then __msg err "argument 1 (device) missing" return 2 # error fi __msg debug "device: ${device}" ## ----- main ----- ## check if device exists if [[ ! -e "${device}" ]]; then __msg err "device '${device}' does not exist" return 2 # error fi ## check if device is a block device local realDevice="$(readlink -f ${device})" __msg debug "realDevice: ${realDevice}" if [[ ! -b "${realDevice}" ]]; then __msg err "device '${device}' does not resolve to a block device" return 2 # error fi env LVM_SUPPRESS_FD_WARNINGS=1 lvs --noheadings -o lv_name "${device}" >>"${_L}" 2>&1 local -i lvsExitCode=${?} if [[ ${lvsExitCode} -ne 0 ]]; then return 1 # check result: negative fi return 0 # check result: positive } # deviceIsLogicalVolume() function deviceIsSnapshotVolume() { ## ----- head ----- ## ## DESCRIPTION: ## checks if the given device is a snapshot volume. ## ## ARGUMENTS: ## 1: device (req) ## ## GLOBAL VARIABLES USED: ## / ## local device=${1} if [[ -z "${device}" ]]; then __msg err "argument 1 (device) missing" return 2 # error fi __msg debug "device: ${device}" ## ----- main ----- ## check if device exists if [[ ! -e "${device}" ]]; then __msg err "device '${device}' does not exist" return 2 # error fi ## check if device is a block device local realDevice="$(readlink -f ${device})" __msg debug "realDevice: ${realDevice}" if [[ ! -b "${realDevice}" ]]; then __msg err "device '${device}' does not resolve to a block device" return 2 # error fi ## get snapshot volume origin local snapshotVolumeOrigin=$( env LVM_SUPPRESS_FD_WARNINGS=1 lvs --separator / --noheadings --nosuffix \ --units m -o origin "${device}" 2>>"${_L}" | xargs -n 1 2>>"${_L}" ) local -i lvsExitCode=${?} if [[ ${lvsExitCode} -ne 0 ]]; then __msg err "failed running 'lvs' to check if device '${device}' is a snapshot volume" return 2 # error fi __msg debug "snapshotVolumeOrigin: ${snapshotVolumeOrigin}" ## empty origin indicates non-snapshot volume if [[ -z ${snapshotVolumeOrigin} ]]; then return 1 # check result: negative fi return 0 # check result: positive } # deviceIsSnapshotVolume() function mountPointIsBindMount() { ## ----- head ----- ## ## DESCRIPTION: ## checks if the given mount point is a bind-mount. ## looks in /etc/mtab as /proc/mounts shows bind-mounts ## as regular mounts . ## ## ARGUMENTS: ## 1: mountPoint (req) ## ## GLOBAL VARIABLES USED: ## / ## local mountPoint=${1} if [[ -z "${mountPoint}" ]]; then __msg err "argument 1 (mountPoint) missing" return 2 # error fi __msg debug "mountPoint: ${mountPoint}" ## ----- main ----- ## check for /etc/mtab if [[ ! -e /etc/mtab ]]; then __msg err "/etc/mtab does not exist" return 2 # error fi ## split /etc/mtab into array by newline IFS=$'\n' local -a mtabMountArray=( $(sort -k 2,2 < /etc/mtab) ) local -a sortExitCode=${?} unset IFS if [[ ${sortExitCode} -ne 0 ]]; then __msg err "failed sorting /etc/mtab" return 2 # error fi ## loop through array of mounts local -i i for ((i = 0; i < ${#mtabMountArray[@]}; i++)); do ## split mount line into fields IFS=' ' set -- ${mtabMountArray[i]} unset IFS #local mtabMountDevice=${1} local mtabMountPoint=${2} #local mtabMountFilesystemType=${3} local mtabMountOpts=${4} set -- #__msg debug "mtabMountPoint: ${mtabMountPoint}; mtabMountOpts: ${mtabMountOpts}" ## check if current mtab entry equals the mount point we're looking for if [[ "${mtabMountPoint}" == "${mountPoint%/}" ]]; then case "${mtabMountOpts}" in ## check if current mtab entry is a bind mount bind|bind,*|*,bind|*,bind,*) return 0 # check result: positive ;; *) continue ;; esac fi done return 1 # check result: negative } # mountPointIsBindMount() function directoryHasMount() { ## ----- head ----- ## ## DESCRIPTION: ## checks if the given directory has something mounted on. ## looks in /proc/mounts. ## ## ARGUMENTS: ## 1: directory (req) ## ## GLOBAL VARIABLES USED: ## / ## local directory=${1} if [[ -z "${directory}" ]]; then __msg err "argument 1 (directory) missing" return 2 # error fi __msg debug "directory: ${directory}" ## ----- main ----- ## check for /proc/mounts if [[ ! -e /proc/mounts ]]; then __msg err "/proc/mounts does not exist" return 2 # error fi ## split /proc/mounts into array by newline IFS=$'\n' local -a procMountArray=( $(sort -k 2,2 < /proc/mounts) ) local -i sortExitCode=${?} unset IFS if [[ ${sortExitCode} -ne 0 ]]; then __msg err "failed sorting /proc/mounts" return 2 # error fi ## loop through array of mounts local -i i for ((i = 0; i < ${#procMountArray[@]}; i++)); do ## split mount line into fields IFS=' ' set -- ${procMountArray[i]} unset IFS #local procMountDevice=${1} local procMountPoint=${2} #local procMountFilesystemType=${3} local procMountOpts=${4} set -- #__msg debug "procMountPoint: ${procMountPoint}; procMountOpts: ${procMountOpts}" ## check if current proc entry equals the mount point we're looking for if [[ "${procMountPoint}" == "${directory%/}" ]]; then return 0 # check result: positive fi done return 1 # check result: negative } # directoryHasMount() function printAllMountedVolumes() { ## ----- head ----- ## ## DESCRIPTION: ## prints a list of all mounted volumes (including bind-mounts and excluding special filesystems). ## looks in /proc/mounts. ## ## ARGUMENTS: ## 1: baseDirectory (opt): / or /mnt/snapshots (default: /) ## 2: sortOrder (opt): 'asc' or 'desc' (default: 'asc') ## ## GLOBAL VARIABLES USED: ## / ## local baseDirectory=${1:-/} local sortOrder=${2:-asc} __msg -q debug "baseDirectory: ${baseDirectory}" __msg -q debug "sortOrder: ${sortOrder}" ## ----- main ----- ## check for /proc/mounts if [[ ! -e /proc/mounts ]]; then __msg -q err "/proc/mounts does not exist" return 2 # error fi ## determine sort arguments based on sort order case ${sortOrder} in asc) local sortArgs= ;; desc) local sortArgs="-r" ;; *) ;; esac ## split /proc/mounts into array by newline IFS=$'\n' local -a procMountArray=( $(sort -k 2,2 ${sortArgs} < /proc/mounts) ) local -i sortExitCode=${?} unset IFS if [[ ${sortExitCode} -ne 0 ]]; then __msg -q err "failed sorting /proc/mounts" return 2 # error fi ## loop through array of mounts local -i i for ((i = 0; i < ${#procMountArray[@]}; i++)); do ## split mount line into fields IFS=' ' set -- ${procMountArray[i]} unset IFS local procMountDevice=${1} local procMountPoint=${2} local procMountFilesystemType=${3} #local procMountOpts=${4} set -- #__msg -q debug "procMountDevice: ${procMountDevice}; procMountPoint: ${procMountPoint}; procMountFilesystemType: ${procMountFilesystemType}" ## check for special filesystem types to skip case "${procMountFilesystemType}" in debugfs|devpts|nfsd|proc|rootfs|securityfs|sysfs|tmpfs|usbfs) continue ;; ext[234]|jfs|reiserfs|xfs) # TODO FIXME: extend list ;; *) continue # TODO FIXME: log unsupported fstype? ;; esac ## check if mount point is given base directory itself ## or below given base directory case "${procMountPoint}" in ${baseDirectory%/}) ;; ${baseDirectory%/}/*) ;; *) continue ;; esac ## finally print device and mount point echo "${procMountDevice}:${procMountPoint}" done return 0 # success } # printAllMountedVolumes() function printAllLogicalVolumes() { ## ----- head ----- ## ## DESCRIPTION: ## prints a list of all logical volumes in the form "VG/LV" ## (without device directory prefix). ## ## ARGUMENTS: ## / ## ## GLOBAL VARIABLES USED: ## / ## ## ----- main ----- env LVM_SUPPRESS_FD_WARNINGS=1 lvs --separator / --noheadings --nosuffix \ --units m -o vg_name,lv_name 2>>"${_L}" | xargs -n 1 2>>"${_L}" local -i lvsExitCode=${PIPESTATUS[0]} local -i xargsExitCode=${PIPESTATUS[1]} if [[ ${lvsExitCode} -ne 0 ]]; then __msg -q err "failed to get list of all logical volumes" return 2 # error fi return 0 # success } # printAllLogicalVolumes() function printLogicalVolumeInfo() { ## ----- head ----- ## ## DESCRIPTION: ## prints the volume group name and logical volume name of a logical volume device. ## ## ARGUMENTS: ## 1: device (req): /dev/mapper/vg.sys-lv.home ## ## GLOBAL VARIABLES USED: ## / ## local device=${1} if [[ -z "${device}" ]]; then __msg -q err "argument 1 (device) missing" return 2 # error fi __msg -q debug "device: ${device}" ## ----- main ----- local logicalVolumeInfo=$(env LVM_SUPPRESS_FD_WARNINGS=1 lvs --separator / --noheadings --nosuffix \ --units m -o vg_name,lv_name "${device}" 2>>"${_L}" | xargs -n 1 2>>"${_L}") local -i lvsExitCode=${PIPESTATUS[0]} local -i xargsExitCode=${PIPESTATUS[1]} if [[ ${lvsExitCode} -ne 0 ]]; then __msg -q err "failed to get logical volume info of device '${device}'" return 2 # error else echo "${logicalVolumeInfo}" fi return 0 # success } # printLogicalVolumeInfo() function logicalVolumeIsExcluded() { ## ----- head ----- ## ## DESCRIPTION: ## checks whether a logical volume is excluded ## ## ARGUMENTS: ## 1: volumeGroupName ## 2: logicalVolumeName ## ## GLOBAL VARIABLES USED: ## LogicalVolumeExcludeArray (fallback: none, may be empty) ## local volumeGroupName=${1} if [[ -z "${volumeGroupName}" ]]; then __msg err "argument 1 (volumeGroupName) missing" return 2 # error fi __msg debug "volumeGroupName: ${volumeGroupName}" local logicalVolumeName=${2} if [[ -z "${logicalVolumeName}" ]]; then __msg err "argument 2 (logicalVolumeName) missing" return 2 # error fi __msg debug "logicalVolumeName: ${logicalVolumeName}" ## ----- main ----- local -i e excludeLogicalVolume=0 for ((e = 0; e < ${#LogicalVolumeExcludeArray[@]}; e++)); do local logicalVolumeExcludeArrayEntry=${LogicalVolumeExcludeArray[e]} __msg debug "logicalVolumeExcludeArrayEntry: ${logicalVolumeExcludeArrayEntry}" case "${logicalVolumeExcludeArrayEntry}" in ## "vg/lv": valid entry */*) IFS='/' set -- ${logicalVolumeExcludeArrayEntry} unset IFS local arrayEntryVolumeGroupName=${1} local arrayEntryLogicalVolumeName=${2} set -- case "${arrayEntryVolumeGroupName}/${arrayEntryLogicalVolumeName}" in "${volumeGroupName}/${logicalVolumeName}") ## we found this vg/lv, so stop searching here let excludeLogicalVolume=1 break ;; *) ## we found a different vg/lv, so continue searching continue ;; esac ;; ## invalid entry *) __msg err "invalid entry in logical volume exclude array: '${logicalVolumeExcludeArrayEntry}'" return 2 # error ;; esac done if [[ ${excludeLogicalVolume} -eq 1 ]]; then return 0 # lv is excluded fi return 1 # lv is not excluded } # logicalVolumeIsExcluded() function printSnapshotVolumeSizeFactor() { ## ----- head ----- ## ## DESCRIPTION: ## prints the snapshot volume size factor of a given vg/lv ## ## ARGUMENTS: ## 1: volumeGroupName ## 2: logicalVolumeName ## ## GLOBAL VARIABLES USED: ## SnapshotVolumeSizeFactorArray (fallback: none) ## local volumeGroupName=${1} if [[ -z "${volumeGroupName}" ]]; then __msg -q err "argument 1 (volumeGroupName) missing" return 2 # error fi __msg -q debug "volumeGroupName: ${volumeGroupName}" local logicalVolumeName=${2} if [[ -z "${logicalVolumeName}" ]]; then __msg -q err "argument 2 (logicalVolumeName) missing" return 2 # error fi __msg -q debug "logicalVolumeName: ${logicalVolumeName}" ## check for global variable: snapshot volume size factor array if [[ "${#SnapshotVolumeSizeFactorArray[@]}" -eq 0 ]]; then __msg -q err "global variable \${SnapshotVolumeSizeFactorArray} is empty" return 2 # error fi ## ----- main ----- local -i s for ((s = 0; s < ${#SnapshotVolumeSizeFactorArray[@]}; s++)); do local snapshotVolumeSizeFactorArrayEntry=${SnapshotVolumeSizeFactorArray[s]} __msg -q debug "snapshotVolumeSizeFactorArrayEntry: ${snapshotVolumeSizeFactorArrayEntry}" case ${snapshotVolumeSizeFactorArrayEntry} in ## "vg/lv:factor" (vg/lv specific) */*:*) IFS=':' set -- ${snapshotVolumeSizeFactorArrayEntry} unset IFS local arrayEntryVolumeGroupName=${1%/*} local arrayEntryLogicalVolumeName=${1#*/} local arrayEntrySnapshotVolumeSizeFactor=${2} set -- case "${arrayEntryVolumeGroupName}/${arrayEntryLogicalVolumeName}" in "${volumeGroupName}/${logicalVolumeName}") local snapshotVolumeSizeFactor="${arrayEntrySnapshotVolumeSizeFactor}" ## we found a specific factor for this vg/lv, ## so stop searching here break ;; *) ## we found a specific factor for a different vg/lv, ## so continue searching continue ;; esac ;; ## "factor" (not vg/lv specific) *) local snapshotVolumeSizeFactor="${snapshotVolumeSizeFactorArrayEntry}" ## we might still find a specific factor, ## so continue searching continue ;; esac done if [[ -z ${snapshotVolumeSizeFactor} ]]; then __msg -q err "failed to get snapshot volume size factor for logical volume '${volumeGroupName}/${logicalVolumeName}' from snapshot volume size array" return 2 # error fi echo ${snapshotVolumeSizeFactor} return 0 # success } # printSnapshotVolumeSizeFactor() function prepareSnapshotVolumeMountDirectory() { ## ----- head ----- ## ## DESCRIPTION: ## creates the snapshot volume mount directory if it doesn't exist. ## ## ARGUMENTS: ## / ## ## GLOBAL VARIABLES USED: ## SnapshotVolumeMountDirectory (fallback: none) ## ## check for snapshot volume mount directory variable if [[ -z "${SnapshotVolumeMountDirectory}" ]]; then __msg err "global variable \${SnapshotVolumeMountDirectory} is empty" return 2 # error ## check for snapshot volume mount directory elif [[ ! -d "${SnapshotVolumeMountDirectory}" ]]; then __msg info "snapshot volume mount directory '${SnapshotVolumeMountDirectory}' doesn't exist -- creating..." ## create snapshot volume mount directory if ! mkdir -p "${SnapshotVolumeMountDirectory}" &>"${_L}"; then __msg err "failed to create snapshot volume mount directory '${SnapshotVolumeMountDirectory}'" return 2 # error fi fi return 0 # success } # prepareSnapshotVolumeMountDirectory() function createSystemMirror() { ## ----- head ----- ## ## DESCRIPTION: ## creates a mirror of all mounted volumes (except bind-mounts and special filesystems). ## ## ARGUMENTS: ## / ## ## GLOBAL VARIABLES USED: ## SnapshotVolumeNameSuffix (fallback: .snapshot) ## SnapshotVolumeMountDirectory ## ## ----- main ----- ## prepare snapshot volume mount directory if ! prepareSnapshotVolumeMountDirectory; then __msg err "failed to prepare snapshot volume mount directory" return 2 # error fi ## get list of all mounted volumes IFS=$'\n' local -a mountedVolumeArray=( $(printAllMountedVolumes "/" "asc") ) local -i returnValue=${?} unset IFS case ${returnValue} in 0) ## check for mounted volumes if [[ ${#mountedVolumeArray[@]} -eq 0 ]]; then __msg err "failed to detect any volumes mounted below / (something's really weird here...)" return 2 # error fi ;; 2) __msg err "failed to print all mounted volumes below /" return 2 # error ;; *) __msg err "undefined return value: ${returnValue}" ## TODO FIXME return 2 # error ;; esac ## 1st step: create snapshots of mounted logical volumes ## 2nd step: mount snapshots of mounted logical volumes, bind-mount all other mounted volumes local createMirrorSteps="createSnapshots mountVolumes" createMirrorStep for createMirrorStep in ${createMirrorSteps}; do local logPrefix="createMirrorStep: ${createMirrorStep};" ## loops through list of all mounted volumes in order to create snapshots local -i i for ((i = 0; i < ${#mountedVolumeArray[@]}; i++)); do IFS=':' set -- ${mountedVolumeArray[i]} unset IFS local device=${1} local mountPoint=${2} set -- __msg debug "${logPrefix} device: ${device}; mountPoint: ${mountPoint}" ## ignore bind-mounts (even bind-mounted logical volumes) if mountPointIsBindMount "${mountPoint}"; then continue ## process logical volumes elif deviceIsLogicalVolume "${device}"; then ## get logical volume info local logicalVolumeInfo=$(printLogicalVolumeInfo "${device}") local -i returnValue=${?} case ${returnValue} in 0) ## extract volume group name and logical volume name ## from logical volume info IFS='/' set -- ${logicalVolumeInfo} unset IFS local volumeGroupName=${1} local logicalVolumeName=${2} set -- __msg debug "${logPrefix} volumeGroupName: ${volumeGroupName}; logicalVolumeName: ${logicalVolumeName}" ;; 2) __msg err "${logPrefix} failed to print logical volume info for device '${device}'" return 2 # error ;; *) __msg err "${logPrefix} undefined return value: ${returnValue}" ## TODO FIXME return 2 # error ;; esac ## determine whether logical volume is excluded logicalVolumeIsExcluded "${volumeGroupName}" "${logicalVolumeName}" local -i returnValue=${?} case ${returnValue} in 0) __msg debug "${logPrefix} logical volume '${volumeGroupName}/${logicalVolumeName}' is excluded" continue ;; 1) __msg debug "${logPrefix} logical volume '${volumeGroupName}/${logicalVolumeName}' is not excluded" ;; 2) __msg err "${logPrefix} failed to check whether logical volume '${volumeGroupName}/${logicalVolumeName}' is excluded" return 2 # error ;; *) __msg err "${logPrefix} undefined return value: ${returnValue}" ## TODO FIXME return 2 # error ;; esac ## generate snapshot volume name local snapshotVolumeName="${logicalVolumeName}${SnapshotVolumeNameSuffix:-.snapshot}" __msg debug "${logPrefix} snapshotVolumeName: ${snapshotVolumeName}" ## create snapshot? if [[ ${createMirrorStep} == "createSnapshots" ]]; then ## determine snapshot volume size factor from snapshot volume size factor array local snapshotVolumeSizeFactor=$(printSnapshotVolumeSizeFactor "${volumeGroupName}" "${logicalVolumeName}") local -i returnValue=${?} case ${returnValue} in 0) __msg debug "${logPrefix} snapshotVolumeSizeFactor: ${snapshotVolumeSizeFactor}" ;; 2) __msg err "${logPrefix} failed to print snapshot volume size factor of logical volume '${volumeGroupName}/${logicalVolumeName}'" return 2 # error ;; *) __msg err "${logPrefix} undefined return value: ${returnValue}" ## TODO FIXME return 2 # error ;; esac ## create snapshot if ! createSnapshot "${volumeGroupName}" "${logicalVolumeName}" "${snapshotVolumeName}" "${snapshotVolumeSizeFactor}"; then __msg err "${logPrefix} failed to create snapshot of logical volume '${volumeGroupName}/${logicalVolumeName}'" return 2 # error fi fi ## mount snapshot? if [[ ${createMirrorStep} == "mountVolumes" ]]; then ## generate mount target directory (subdirectory of snapshot volume mount directory) local mountTargetDirectory="${SnapshotVolumeMountDirectory}/${mountPoint#/}" __msg debug "${logPrefix} mountTargetDirectory: ${mountTargetDirectory}" ## mount snapshot if ! mountSnapshot "${volumeGroupName}" "${snapshotVolumeName}" "${mountTargetDirectory}"; then __msg err "${logPrefix} failed to mount snapshot volume '${volumeGroupName}/${snapshotVolumeName}' on '${mountTargetDirectory}'" return 2 # error fi fi ## bind-mount all other (physical) volumes else ## bind-mount directory if [[ ${createMirrorStep} == "mountVolumes" ]]; then local mountTargetDirectory="${SnapshotVolumeMountDirectory}/${mountPoint#/}" __msg debug "${logPrefix} mountTargetDirectory: ${mountTargetDirectory}" if ! bindMountDirectory "${mountPoint}" "${mountTargetDirectory}"; then __msg err "${logPrefix} failed to mount directory '${mountPoint}' on '${mountTargetDirectory}'" return 2 # error fi fi fi done done return 0 # success } # createSystemMirror() function deleteSystemMirror() { ## ----- head ----- ## ## DESCRIPTION: ## deletes a mirror of all mounted volumes. ## ## ARGUMENTS: ## / ## ## GLOBAL VARIABLES USED: ## SnapshotVolumeMountDirectory ## ## check for snapshot volume mount directory if [[ ! -d "${SnapshotVolumeMountDirectory}" ]]; then __msg err "snapshot volume mount directory does not exist" return 2 # error fi ## get list of all mounted volumes below snapshot volume mount directory ## in descending order (suitable for unmounting) IFS=$'\n' local -a mountedVolumeArray=( $(printAllMountedVolumes "${SnapshotVolumeMountDirectory}" "desc") ) unset IFS ## check for mounted volumes if [[ ${#mountedVolumeArray[@]} -eq 0 ]]; then __msg err "no volumes mounted below snapshot volume mount directory '${SnapshotVolumeMountDirectory}'" return 2 # error fi ## 1st step: create snapshots of mounted logical volumes ## 2nd step: mount snapshots of mounted logical volumes, bind-mount all other mounted volumes local deleteMirrorSteps="unmountVolumes deleteSnapshots" deleteMirrorStep for deleteMirrorStep in ${deleteMirrorSteps}; do local logPrefix="deleteMirrorStep: ${deleteMirrorStep};" ## loops through list of all mounted volumes in order to create snapshots local -i i for ((i = 0; i < ${#mountedVolumeArray[@]}; i++)); do IFS=':' set -- ${mountedVolumeArray[i]} unset IFS local device=${1} local mountPoint=${2} set -- __msg debug "${logPrefix} device: ${device}; mountPoint: ${mountPoint}" ## if device is a snapshot volume, delete it if deviceIsSnapshotVolume "${device}"; then ## get logical volume info local logicalVolumeInfo=$(printLogicalVolumeInfo "${device}") local -i returnValue=${?} case ${returnValue} in 0) ## extract volume group name and logical volume name ## from logical volume info IFS='/' set -- ${logicalVolumeInfo} unset IFS local volumeGroupName=${1} local logicalVolumeName=${2} set -- __msg debug "${logPrefix} volumeGroupName: ${volumeGroupName}; logicalVolumeName: ${logicalVolumeName}" ;; 2) __msg err "${logPrefix} failed to print logical volume info for device '${device}'" return 2 # error ;; *) __msg err "${logPrefix} undefined return value: ${returnValue}" ## TODO FIXME return 2 # error ;; esac ## delete snapshot if [[ ${deleteMirrorStep} == "deleteSnapshots" ]]; then local snapshotVolumeName=${logicalVolumeName} if ! deleteSnapshot "${volumeGroupName}" "${snapshotVolumeName}"; then __msg err "${logPrefix} failed to delete snapshot volume '${volumeGroupName}/${snapshotVolumeName}'" return 2 # error fi fi fi ## unmount volumes if [[ ${deleteMirrorStep} == "unmountVolumes" ]]; then if ! unmount "${mountPoint}"; then __msg err "${logPrefix} failed to unmount '${mountPoint}'" return 2 # error fi fi done done return 0 # success } # deleteSystemMirror()