aboutsummaryrefslogtreecommitdiff
path: root/tests/agent/btrfs-cpdir
blob: d2dbd435737e2bfb73c2653ed5860cf8e9c556c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#! /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 <dst> exists, it is first removed by
# calling btrfs-rmdir (which is expected to be found next to btrfs-cpdir).
#
# Notes:
#
#  1. <src> 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 <src> is clones as <dst>, not into <dst> (i.e., like cp -T).
#
usage="usage: $0 [-f] <src>/ <dst>/"

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 () # <src> <dst>
{
  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 () # <src> <dst>
{
  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"