#! /usr/bin/env bash # Copy a directory on (the same) btrfs filesystem. Inside, subvolumes # are copied with btrfs subvolume snapshot and everything else with # cp --reflink=always. # # If the -f option is specified and exists, it is first removed by # calling btrfs-rmdir (which is expected to be found next to btrfs-cpdir). # # Notes: # # 1. should not be a subvolume (use snapshot directly in this case). # # 2. Copying of symlinks is not supported (symlinks in submodules are ok). # # 3. Read-only subvolumes are snapshotted as read-only. # # Note also that is clones as , not into (i.e., like cp -T). # usage="usage: $0 [-f] / /" owd="$(pwd)" trap "{ cd '$owd'; exit 1; }" ERR set -o errtrace # Trap in functions. function info () { echo "$*" 1>&2; } function error () { info "$*"; exit 1; } force= while [ "$#" -gt 0 ]; do case "$1" in -f) shift force="true" ;; --) break ;; -*) error "unknown option: $1" ;; *) break ;; esac done src="${1%/}" dst="${2%/}" if [ -z "$src" -o -z "$dst" ]; then error "$usage" fi shopt -s nullglob dotglob function cp_dir () # { local src="$1" local dst="$2" mkdir "$dst" local s d for s in "$src"/*; do d="$dst/${s#$src/}" if [ -f "$s" ]; then cp -p --reflink=always "$s" "$d" continue fi if [ -d "$s" ]; then # See if this is a subvolume: btrfs subvolume list requires root # priviliges so we use the inode number which for subvolumes is always # 256. # if [ "$(stat --format=%i "$s")" -eq 256 ]; then cp_subvol "$s" "$d" else cp_dir "$s" "$d" fi continue fi error "$s is not a file/directory, not supported" done chmod --reference="$src" "$dst" chown --reference="$src" "$dst" } function cp_subvol () # { local src="$1" local dst="$2" local o=() if [ "$(btrfs property get -ts "$src" ro)" = "ro=true" ]; then o+=(-r) fi btrfs subvolume snapshot "${o[@]}" "$src" "$dst" >/dev/null } if [ -d "$dst" -a -n "$force" ]; then "$(dirname "$(realpath ${BASH_SOURCE[0]})")/btrfs-rmdir" "$dst" fi cp_dir "$src" "$dst"