Current File : //proc/self/root/usr/local/jetapps/usr/share/rear/layout/save/GNU/Linux/230_filesystem_layout.sh
# Save Filesystem layout
Log "Begin saving filesystem layout"
# If available wipefs is used in the recovery system by 130_include_filesystem_code.sh
# as a generic way to cleanup disk partitions before creating a filesystem on a disk partition,
# see https://github.com/rear/rear/issues/540
# and https://github.com/rear/rear/issues/649#issuecomment-148725865
# Therefore if wipefs exists here in the original system it is added to REQUIRED_PROGS
# so that it will become also available in the recovery system (cf. 260_crypt_layout.sh):
has_binary wipefs && REQUIRED_PROGS+=( wipefs ) || true
# Comma separated list of filesystems that is used for "mount/findmnt -t <list,of,filesystems>" below:
supported_filesystems="ext2,ext3,ext4,vfat,xfs,reiserfs,btrfs"
# Read filesystem information from the system by default using the traditional mount command
# limited to only the supported filesystems which results output lines of the form
#   device mountpoint filesystem (list,of,options)
# for example
#   /dev/sda2 / btrfs (rw,relatime,space_cache)
#   /dev/sda2 /.snapshots btrfs (rw,relatime,space_cache)
#   /dev/sda2 /var/tmp btrfs (rw,relatime,space_cache)
read_filesystems_command="mount -t $supported_filesystems | cut -d ' ' -f 1,3,5,6"
# If the findmnt command is available use it instead of the traditional mount command
# because (since SLE12) "man 8 mount" reads:
#   The listing mode is maintained for backward compatibility only.
#   For more robust and customizable output use findmnt(8), especially in your scripts.
# It is limited to only the supported filesystems which results output lines of the form
#   device mountpoint filesystem list,of,options
# for example
#   /dev/sda2 / btrfs rw,relatime,space_cache
#   /dev/sda2 /.snapshots btrfs rw,relatime,space_cache
#   /dev/sda2 /var/tmp btrfs rw,relatime,space_cache
# The only difference is that the traditional mount command output has the list of options in parenthesis.
findmnt_command="$( type -P findmnt )"
if test -x "$findmnt_command" ; then
    # Use the (deprecated) "findmnt -m" to avoid issues
    # as in https://github.com/rear/rear/issues/882
    # FIXME: Replace using the deprecated '-m' option with a future proof solution.
    read_filesystems_command="$findmnt_command -mnrv -o SOURCE,TARGET,FSTYPE,OPTIONS -t $supported_filesystems"
    Log "Saving filesystem layout (using the findmnt command)."
else
    Log "Saving filesystem layout (using the traditional mount command)."
fi
# Remove duplicate lines for the same device:
#   -t ' ' space is the field delimiter
#   -k 1,1 the sort key starts and ends at field 1 (i.e. device is the only sort key)
#   -u     unique regarding the sort key (i.e. remove duplicate lines regarding the sort key)
# so that in the above example the resulting output using the traditional mount command is
#   /dev/sda2 / btrfs (rw,relatime,space_cache)
# and the resulting output using using the findmnt command is
#   /dev/sda2 / btrfs rw,relatime,space_cache
# The sorting relies on that mount and findmnt output the first mounted thing first
# so that in particular what is mounted at '/' is output before other stuff.
read_filesystems_command+=" | sort -t ' ' -k 1,1 -u"

# The Docker daemon mounts file systems for its Docker containers, see also
# https://docs.docker.com/storage/storagedriver/device-mapper-driver/#configure-direct-lvm-mode-for-production
# As it is for container usage only we do not to backup these up or recreate as this disk device is completely
# under control by Docker itself (even the recreation of it incl, the creation of the volume group).
# Usually this is done via a kind of cookbook (Chef, puppet or ansible).
docker_is_running=""
docker_root_dir=""
if service docker status ; then
    docker_is_running="yes"
    # When the Docker daemon/service is running, try to get its 'Docker Root Dir':
    # Kill 'docker info' with SIGTERM after 10 seconds and with SIGKILL after additional 2 seconds
    # because there are too many crippled Docker installations, cf. https://github.com/rear/rear/pull/2021
    # and https://github.com/rear/rear/pull/2572#issuecomment-784110872 why the timeout is 10 seconds
    # i.e. it seems sometimes 'docker info' needs several (more than 5) seconds to finish:
    docker_root_dir=$( timeout -k 2s 10s docker info | grep 'Docker Root Dir' | awk '{print $4}' )
    # Things may go wrong in the 'Docker specific exclude part' below
    # when Docker is used but its 'Docker Root Dir' cannot be determined
    # cf. https://github.com/rear/rear/issues/1989
    if test "$docker_root_dir" ; then
        LogPrint "Docker is running, skipping filesystems mounted below Docker Root Dir $docker_root_dir"
    else
        LogPrintError "Cannot determine Docker Root Dir - things may go wrong - check $DISKLAYOUT_FILE"
    fi
fi

# Begin of group command that appends its stdout to DISKLAYOUT_FILE:
{
    echo "# Filesystems (only $supported_filesystems are supported)."
    echo "# Format: fs <device> <mountpoint> <fstype> [uuid=<uuid>] [label=<label>] [<attributes>]"
    # Read the output of the read_filesystems_command:
    while read device mountpoint fstype options junk ; do
        Log "Processing filesystem '$fstype' on '$device' mounted at '$mountpoint'"
        # Empty device or mountpoint or fstype may may indicate an error. In this case be verbose and inform the user:
        if test -z "$device" -o -z "$mountpoint" -o -z "$fstype" ; then
            LogPrintError "Empty device='$device' or mountpoint='$mountpoint' or fstype='$fstype', skipping saving filesystem layout for it."
            continue
        fi
        # FIXME: I (jsmeix@suse.de) have no idea what the reason for the following is.
        # In an ancient code version, the full mount command output was parsed line by line
        # At that time the code was [ "${line#/}" = "$line" ]
        # Then it skipped each mount line that didn't start with a forward slash (/)
        # https://github.com/rear/rear/blame/e8de02b1c791f4d6b3de6d0d38529eb72375c2f6/usr/share/rear/layout/save/GNU/Linux/23_filesystem_layout.sh
        if [ "${device#/}" = "$device" ] ; then
            Log "\${device#/} = '${device#/}' = \$device, skipping."
            continue
        fi
        # Skip saving filesystem layout for non-block devices:
        if [ ! -b "$device" ] ; then
            Log "$device is not a block device, skipping."
            continue
        fi
        # Skip saving filesystem layout for CD/DVD type devices:
        if [ "$fstype" = "iso9660" ] ; then
            Log "$device is CD/DVD type device [fstype=$fstype], skipping."
            continue
        fi
        # Docker specific exclude part:
        if is_true $docker_is_running ; then
            # If docker_root_dir is the beginning of the mountpoint string then the filesystem is under Docker control
            # and we better exclude it from saving the layout, see https://github.com/rear/rear/issues/1749
            # but ensure docker_root_dir is not empty (otherwise any mountpoint string matches "^" which
            # would skip all mountpoints), see https://github.com/rear/rear/issues/1989#issuecomment-456054278
            if test "$docker_root_dir" ; then
                if echo "$mountpoint" | grep -q "^${docker_root_dir}/" ; then
                    Log "Filesystem $fstype on $device mounted at $mountpoint is below Docker Root Dir $docker_root_dir, skipping."
                    continue
                fi
                # In case Longhorn is rebuilding a replica device it will show up as a pseudo-device and when that is the
                # case then you would find traces of it in the /var/lib/rear/layout/disklayout.conf file, which would
                # break the recovery as Longhorn Engine replica's are under control of Rancher Longhorn software and these are
                # rebuild automatically via kubernetes longhorn-engine pods.
                # Issue where we discovered this behavior was #2365
                # In normal situations you will find traces of longhorn in the log saying skipping non-block devices.
                # For example an output of the 'df' command:
                # /dev/longhorn/pvc-ed09c0f2-c086-41c8-a38a-76ee8c289792   82045336    4500292   77528660   6% /var/lib/kubelet/pods/7f47aa55-30e2-4e7b-8fec-ec9a1e761352/volumes/kubernetes.io~csi/pvc-ed09c0f2-c086-41c8-a38a-76ee8c289792/mount
                # lsscsi shows it as:
                # [34:0:0:0]   storage IET      Controller       0001  -
                # [34:0:0:1]   disk    IET      VIRTUAL-DISK     0001  /dev/sdf
                # ls -l /dev/sdf /dev/longhorn/pvc-ed09c0f2-c086-41c8-a38a-76ee8c289792
                # brw-rw---- 1 root disk 8, 80 Apr 17 12:02 /dev/sdf
                # brw-rw---- 1 root root 8, 64 Apr 17 10:36 /dev/longhorn/pvc-ed09c0f2-c086-41c8-a38a-76ee8c289792
                # and parted says:
                # parted /dev/longhorn/pvc-ed09c0f2-c086-41c8-a38a-76ee8c289792 print
                # Model: IET VIRTUAL-DISK (scsi)
                # Disk /dev/longhorn/pvc-ed09c0f2-c086-41c8-a38a-76ee8c289792: 85.9GB
                # Sector size (logical/physical): 512B/512B
                # Partition Table: loop
                # Disk Flags:
                # Number  Start  End     Size    File system  Flags
                # 1      0.00B  85.9GB  85.9GB  ext4
                # => as result (without the next if clausule) we would end up with an entry in the disklayout.conf file:
                # fs /dev/longhorn/pvc-ed09c0f2-c086-41c8-a38a-76ee8c289792 /var/lib/kubelet/pods/61ed399a-d51b-40b8-8fe8-a78e84a1dd0b/volumes/kubernetes.io~csi/pvc-c65df331-f1c5-466a-9731-b2aa5e6da714/mount ext4 uuid=4fafdd40-a9ae-4b62-8bfb-f29036dbe3b9 label= blocksize=4096 reserved_blocks=0% max_mounts=-1 check_interval=0d bytes_per_inode=16384 default_mount_options=user_xattr,acl options=rw,relatime,data=ordered
                if echo "$device" | grep -q "^/dev/longhorn/pvc-" ; then
                    Log "Longhorn Engine replica $device, skipping."
                    continue
                fi
            fi
        fi
        # Replace a symbolic link /dev/disk/by-uuid/a1b2c3 -> ../../sdXn
        # by the fully canonicalized target of the link e.g. /dev/sdXn
        if [[ $device == /dev/disk/by-uuid* ]]; then
            # Canonicalize by following every symlink in every component of /dev/disk/by-uuid... and all components must exist:
            ndevice=$(readlink -e $device)
            Log "Mapping $device to $ndevice"
            device=$ndevice
        fi
        # FIXME: is the above condition still needed if the following is in place?
        # get_device_name and get_device_name_mapping below should canonicalize obscured udev names

        # Work with the persistent dev name:
        # Address the fact than dm-XX may be different disk in the recovery environment.
        # See https://github.com/rear/rear/pull/695
        device=$( get_device_mapping $device )
        device=$( get_device_name $device )

        # Output generic filesystem layout values:
        echo -n "fs $device $mountpoint $fstype"
        # Output filesystem specific layout values:
        case "$fstype" in
            # Use leading parenthesis for the cases to have pairs of matching parenthesis in the script:
            (ext*)
                tunefs="tune2fs"
                # on RHEL 5 tune2fs does not work on ext4, needs tune4fs
                if [ "$fstype" = "ext4" ] ; then
                    if ! tune2fs -l $device >/dev/null; then
                        tunefs="tune4fs"
                    fi
                fi
                uuid=$( $tunefs -l $device | tr -d '[:blank:]' | grep -i 'UUID:' | cut -d ':' -f 2 )
                echo -n " uuid=$uuid"
                label=$( e2label $device )
                echo -n " label=$label"
                # options: blocks, fragments, max_mount, check_interval, reserved blocks, bytes_per_inode
                blocksize=$( $tunefs -l $device | tr -d '[:blank:]' | grep -i 'Blocksize:[0-9]*' | cut -d ':' -f 2 )
                echo -n " blocksize=$blocksize"
                # we agreed to comment fragmentsize due mkfs.ext* option -f not existing (man page says it is) - issue #558
                #fragmentsize=$( $tunefs -l $device | tr -d '[:blank:]' | grep -oi 'Fragmentsize:[0-9]*' | cut -d ':' -f 2 )
                #echo -n " fragmentsize=$fragmentsize"
                nr_blocks=$( $tunefs -l $device | tr -d '[:blank:]' | grep -iv reserved | grep -i 'Blockcount:[0-9]*' | cut -d ':' -f 2 )
                reserved_blocks=$( $tunefs -l $device | tr -d '[:blank:]' | grep -i 'Reservedblockcount:[0-9]*' | cut -d ':' -f 2 )
                reserved_percentage=$(( reserved_blocks * 100 / nr_blocks ))
                StopIfError "Divide by zero detected"
                echo -n " reserved_blocks=$reserved_percentage%"
                max_mounts=$( $tunefs -l $device | tr -d '[:blank:]' | grep -i 'Maximummountcount:[0-9]*' | cut -d ':' -f 2 )
                echo -n " max_mounts=$max_mounts"
                check_interval=$( $tunefs -l $device | tr -d '[:blank:]' | grep -i 'Checkinterval:[0-9]*' | cut -d ':' -f 2 | cut -d '(' -f1 )
                # is_integer outputs '0' if its (first) argument is not an integer (or empty)
                check_interval=$( is_integer $check_interval )
                # translate check_interval from seconds to days
                let check_interval=$check_interval/86400
                echo -n " check_interval=${check_interval}d"
                nr_inodes=$( $tunefs -l $device | tr -d '[:blank:]' | grep -i 'Inodecount:[0-9]*' | cut -d ':' -f 2 )
                let "bytes_per_inode=$nr_blocks*$blocksize/$nr_inodes"
                StopIfError "Divide by zero detected"
                echo -n " bytes_per_inode=$bytes_per_inode"
                default_mount_options=$( tune2fs -l $device | grep -i "Default mount options:" | cut -d ':' -f 2 | awk '{$1=$1};1' | tr ' ' ',' | grep -v none )
                if [[ -n $default_mount_options ]]; then
                    echo -n " default_mount_options=$default_mount_options"
                fi
                ;;
            (vfat)
                label=$(blkid_label_of_device $device)
                uuid=$(blkid_uuid_of_device $device)
                echo -n " uuid=$uuid label=$label"
                ;;
            (xfs)
                uuid=$(xfs_admin -u $device | cut -d'=' -f 2 | tr -d " ")
                label=$(xfs_admin -l $device | cut -d'"' -f 2)
                echo -n " uuid=$uuid label=$label "
                # Save current XFS file system options.
                # Saved options will be later used in ReaR recovery system for
                # file system re-creation.
                xfs_info $mountpoint > $LAYOUT_XFS_OPT_DIR/$(basename ${device}.xfs)
                StopIfError "Failed to save XFS options of $device"
                ;;
            (reiserfs)
                uuid=$(debugreiserfs $device | grep "UUID" | cut -d":" -f "2" | tr -d " ")
                label=$(debugreiserfs $device | grep "LABEL" | cut -d":" -f "2" | tr -d " ")
                echo -n " uuid=$uuid label=$label"
                ;;
            (btrfs)
                # FIXME: Support for multi-disk BTRFS should be implemented sooner or later
                # because it is the default layout for multi-disk Fedora Workstation installations.
                # See: https://github.com/rear/rear/issues/2028
                if grep -qE 'Total devices ([2-9]|[1-9][0-9]+) ' <(btrfs filesystem show "$mountpoint"); then
                    Error "Mounpoint $mountpoint points to a BTRFS filesystem spanning multiple disk devices which is not yet supported. See: https://github.com/rear/rear/issues/2028"
                fi

                # Remember devices and mountpoints of the btrfs filesystems for the btrfs subvolume layout stuff below:
                btrfs_devices_and_mountpoints+=" $device,$mountpoint"
                uuid=$( btrfs filesystem show $device | grep -o 'uuid: .*' | cut -d ':' -f 2 | tr -d '[:space:]' )
                label=$( btrfs filesystem show $device | grep -o 'Label: [^ ]*' | cut -d ':' -f 2 | tr -d '[:space:]' )
                test "none" = "$label" && label=
                echo -n " uuid=$uuid label=$label"
                ;;
        esac
        # Remove parenthesis (from the traditional mount command output) from the list of options:
        options=${options#(}
        options=${options%)}

        #clip out the "seclabel" option to avoid problems. See issue no.545
        options=${options//seclabel,/}

        echo -n " options=$options"
        # Finish the current filesystem layout line with a newline character:
        echo
    done < <( eval $read_filesystems_command )

    # Begin btrfs subvolume layout if a btrfs filesystem exists:
    if test -n "$btrfs_devices_and_mountpoints" ; then
        btrfs_subvolume_sles_setup_devices=""
        ########################################
        # Btrfs subvolumes (regardless if mounted or not):
        for btrfs_device_and_mountpoint in $btrfs_devices_and_mountpoints ; do
            # Assume $btrfs_device_and_mountpoint is "/dev/sdX99,/my/mount,point" then split
            # at the first comma because device nodes (e.g. /dev/sdX99) do not contain a comma
            # but a mount point directory name may contain a comma (e.g. /my/mount,point).
            # If a mount point directory name contains space or tab characters it will break here
            # because space tab and newline are standard bash internal field separators ($IFS)
            # so that admins who use such characters for their files or directories get hereby
            # an exercise in using fail-safe names and/or how to enhance standard bash scripts:
            btrfs_device=${btrfs_device_and_mountpoint%%,*}
            btrfs_mountpoint=${btrfs_device_and_mountpoint#*,}
            ####################################
            # Btrfs default subvolume:
            echo "# Btrfs default subvolume for $btrfs_device at $btrfs_mountpoint"
            echo "# Format: btrfsdefaultsubvol <device> <mountpoint> <btrfs_subvolume_ID> <btrfs_subvolume_path>"
            # The command:           btrfs subvolume get-default /
            # results on SLES 12:    ID 257 gen 6733 top level 5 path @
            # and on openSUSE 13.2:  ID 5 (FS_TREE)
            # and on Fedora 21:      ID 5 (FS_TREE)
            btrfs_default_subvolume_ID=$( btrfs subvolume get-default $btrfs_mountpoint | tr -s '[:blank:]' ' ' | cut -d ' ' -f 2 )
            btrfs_default_subvolume_path=$( btrfs subvolume get-default $btrfs_mountpoint | tr -s '[:blank:]' ' ' | cut -d ' ' -f 9 )
            # If there is no field 9 the default subvolume path is the filesystem root (called "top-level subvolume" or "FS_TREE" by btrfs).
            # Denote the btrfs filesystem root by '/' (the only character that is really forbidden in directory names).
            # Do not denote the filesystem root by 'FS_TREE' or by any word that is a valid directory name or btrfs subvolume name
            # because an admin can create a btrfs subvolume with name 'FS_TREE' via: btrfs subvolume create FS_TREE
            test -z "$btrfs_default_subvolume_path" && btrfs_default_subvolume_path="/"
            # Empty btrfs_default_subvolume_ID may may indicate an error. In this case be verbose and inform the user:
            if test -z "$btrfs_default_subvolume_ID" ; then
                LogPrintError "Empty btrfs_default_subvolume_ID, no btrfs default subvolume stored for $btrfs_device at $btrfs_mountpoint"
            else
                echo "btrfsdefaultsubvol $btrfs_device $btrfs_mountpoint $btrfs_default_subvolume_ID $btrfs_default_subvolume_path"
            fi
            ####################################
            # Btrfs snapshot subvolumes:
            # In case of errors "btrfs subvolume list" results output on stderr but none on stdout
            # so that the following test intentionally also fails in case of errors:
            if test $( btrfs subvolume list -as $btrfs_mountpoint | wc -l ) -gt 0 ; then
                snapshot_subvolume_list=$( btrfs subvolume list -as $btrfs_mountpoint | tr -s '[:blank:]' ' ' | cut -d ' ' -f 2,14 | sed -e 's/<FS_TREE>\///' )
                prefix=$( echo "#btrfssnapshotsubvol $btrfs_device $btrfs_mountpoint" | sed -e 's/\//\\\//g' )
                echo "# Btrfs snapshot subvolumes for $btrfs_device at $btrfs_mountpoint"
                echo "# Btrfs snapshot subvolumes are listed here only as documentation."
                echo "# There is no recovery of btrfs snapshot subvolumes."
                echo "# Format: btrfssnapshotsubvol <device> <mountpoint> <btrfs_subvolume_ID> <btrfs_subvolume_path>"
                echo "$snapshot_subvolume_list" | sed -e "s/^/$prefix /"
            fi
            ####################################
            # Btrfs normal subvolumes:
            # Btrfs normal subvolumes are btrfs subvolumes that are no snapshot subvolumes.
            # In case of errors "btrfs subvolume list" results output on stderr but none on stdout
            # so that the following test intentionally also fails in case of errors:
            if test $( btrfs subvolume list -a $btrfs_mountpoint | wc -l ) -gt 0 ; then
                subvolume_list=$( btrfs subvolume list -a $btrfs_mountpoint | tr -s '[:blank:]' ' ' | cut -d ' ' -f 2,9 | sed -e 's/<FS_TREE>\///' )
                prefix=$( echo "btrfsnormalsubvol $btrfs_device $btrfs_mountpoint" | sed -e 's/\//\\\//g' )
                # Get the IDs of the snapshot subvolumes as pattern for "grep -Ev" e.g. like
                #   grep -Ev '^279 |^280 |^281 |^282 |^285 |^286 |^289 |^290 '
                # to exclude snapshot subvolume lines to get only the normal subvolumes.
                # btrfs subvolume IDs are only unique for one same btrfs filesystem
                # which is the case here because the btrfs_device_and_mountpoint is fixed herein
                # and on one btrfs_device (e.g. /dev/sda2) there is only one btrfs filesystem.
                # The following " sed | tr | sed " pipe is ugly ( simplification is left as an exercise for the reader ;-)
                snapshot_subvolumes_pattern=$( btrfs subvolume list -as $btrfs_mountpoint | tr -s '[:blank:]' ' ' | cut -d ' ' -f 2 | sed -e 's/^/^/' -e 's/$/ |/' | tr -d '\n' | sed -e 's/|$//' )
                # Exclude snapshot subvolumes (if exist).
                # When there are no snapshot subvolumes $snapshot_subvolumes_pattern variable will be empty.
                # This special case must be handled properly when setting up $subvolumes_exclude_pattern
                # otherwise ReaR would not recreate the btrfs subvolumes during recovery
                # because an empty pattern in the below grep -Ev '|...' command would
                # exclude all lines (see https://github.com/rear/rear/pull/1435):
                if test -z "$snapshot_subvolumes_pattern" ; then
                    subvolumes_exclude_pattern=""
                else
                    subvolumes_exclude_pattern="$snapshot_subvolumes_pattern"
                fi
                # Output header:
                echo "# Btrfs normal subvolumes for $btrfs_device at $btrfs_mountpoint"
                echo "# Format: btrfsnormalsubvol <device> <mountpoint> <btrfs_subvolume_ID> <btrfs_subvolume_path>"
                # SLES 12 SP1 (and later) special btrfs subvolumes setup detection
                # cf. similar code in layout/prepare/GNU/Linux/130_include_mount_subvolumes_code.sh
                SLES12SP1_btrfs_detection_string="@/.snapshots/"
                if btrfs subvolume get-default $btrfs_mountpoint | grep -q "$SLES12SP1_btrfs_detection_string" ; then
                    info_message="SLES12-SP1 (and later) btrfs subvolumes setup needed for $btrfs_device (default subvolume path contains '$SLES12SP1_btrfs_detection_string')"
                    if is_false "$BTRFS_SUBVOLUME_SLES_SETUP" ; then
                        Error "BTRFS_SUBVOLUME_SLES_SETUP is false but $info_message"
                    fi
                    LogPrint $info_message
                    echo "# $info_message"
                    # Append all btrfs filesystem device nodes with SLES12-SP1 (and later) btrfs subvolumes setup needed
                    # to the BTRFS_SUBVOLUME_SLES_SETUP array (unless such a device is already in that array)
                    # to enforce during "rear recover" btrfs_subvolumes_setup_SLES() is called to setup that btrfs filesystem
                    # regardless what there already is in BTRFS_SUBVOLUME_SLES_SETUP - e.g. if BTRFS_SUBVOLUME_SLES_SETUP=( 'false' )
                    # but /dev/sda3 is a btrfs filesystem device node with SLES12-SP1 (and later) btrfs subvolumes setup needed
                    # it will become BTRFS_SUBVOLUME_SLES_SETUP=( 'false' '/dev/sda3' ) which avoids btrfs_subvolumes_setup_SLES()
                    # for all devices except '/dev/sda3' where btrfs_subvolumes_setup_SLES() is called to setup that btrfs filesystem
                    # cf. https://github.com/rear/rear/pull/2080#discussion_r265046317 and see the code in the
                    # usr/share/rear/layout/prepare/GNU/Linux/133_include_mount_filesystem_code.sh script:
                    IsInArray "$btrfs_device" "${BTRFS_SUBVOLUME_SLES_SETUP[@]}" || btrfs_subvolume_sles_setup_devices+=" $btrfs_device"
                    # SLES 12 SP1 (or later) normal subvolumes that belong to snapper are excluded from being recreated:
                    # Snapper's base subvolume '/@/.snapshots' is excluded because during "rear recover"
                    # that one will be created by "snapper/installation-helper --step 1" which fails if it already exists
                    # (see the code in layout/prepare/GNU/Linux/130_include_mount_subvolumes_code.sh).
                    # Furthermore any normal btrfs subvolume under snapper's base subvolume '/@/.snapshots' is wrong
                    # (see https://github.com/rear/rear/issues/944#issuecomment-238239926
                    # and https://github.com/rear/rear/issues/963).
                    # Because any btrfs subvolume under '@/.snapshots/' lets "snapper/installation-helper --step 1" fail
                    # any btrfs subvolume under '@/.snapshots/' is excluded here from being recreated
                    # to not let "rear recover" fail because of such kind of wrong btrfs subvolumes:
                    snapper_base_subvolume="@/.snapshots"
                    # Exclude usual snapshot subvolumes and subvolumes that belong to snapper.
                    # When SLES12 SP1 (or later) is setup to use btrfs without snapshots
                    # $snapshot_subvolumes_pattern variable will be empty. This special case
                    # must be handled properly when setting up $subvolumes_exclude_pattern
                    # otherwise ReaR would not recreate the btrfs subvolumes during recovery
                    # because an empty pattern in the below grep -Ev '|...' command would
                    # exclude all lines (see https://github.com/rear/rear/pull/1435):
                    if test -z "$snapshot_subvolumes_pattern" ; then
                        subvolumes_exclude_pattern="$snapper_base_subvolume"
                    else
                        subvolumes_exclude_pattern="$snapshot_subvolumes_pattern|$snapper_base_subvolume"
                    fi
                    # List subvolumes that belong to snapper as comments (deactivated) if such subvolumes exist.
                    # Have them before the other btrfs normal subvolumes because a single comment block looks less confusing
                    # and matches better to the directly before listed (deactivated) snapshot subvolumes comments:
                    if btrfs subvolume list -a $btrfs_mountpoint | grep -q "$snapper_base_subvolume" ; then
                        echo "# Btrfs subvolumes that belong to snapper are listed here only as documentation."
                        echo "# Snapper's base subvolume '/@/.snapshots' is deactivated here because during 'rear recover'"
                        echo "# it is created by 'snapper/installation-helper --step 1' (which fails if it already exists)."
                        echo "# Furthermore any normal btrfs subvolume under snapper's base subvolume would be wrong."
                        echo "# See https://github.com/rear/rear/issues/944#issuecomment-238239926"
                        echo "# and https://github.com/rear/rear/issues/963#issuecomment-240061392"
                        echo "# how to create a btrfs subvolume in compliance with the SLES12 default brtfs structure."
                        echo "# In short: Normal btrfs subvolumes on SLES12 must be created directly below '/@/'"
                        echo "# e.g. '/@/var/lib/mystuff' (which requires that the btrfs root subvolume is mounted)"
                        echo "# and then the subvolume is mounted at '/var/lib/mystuff' to be accessible from '/'"
                        echo "# plus usually an entry in /etc/fstab to get it mounted automatically when booting."
                        echo "# Because any '@/.snapshots' subvolume would let 'snapper/installation-helper --step 1' fail"
                        echo "# such subvolumes are deactivated here to not let 'rear recover' fail:"
                        if test -z "$snapshot_subvolumes_pattern" ; then
                            # With an empty snapshot_subvolumes_pattern grep -Ev '' would exclude all lines:
                            echo "$subvolume_list" | grep "$snapper_base_subvolume" | sed -e "s/^/#$prefix /"
                        else
                            echo "$subvolume_list" | grep -Ev "$snapshot_subvolumes_pattern" | grep "$snapper_base_subvolume" | sed -e "s/^/#$prefix /"
                        fi
                    fi
                fi
                # Output btrfs normal subvolumes:
                if test -z "$subvolumes_exclude_pattern" ; then
                    # With an empty subvolumes_exclude_pattern grep -Ev '' would exclude all lines:
                    echo "$subvolume_list" | sed -e "s/^/$prefix /"
                else
                    echo "$subvolume_list" | grep -Ev "$subvolumes_exclude_pattern" | sed -e "s/^/$prefix /"
                fi
            fi
        done
        ########################################
        # Mounted btrfs subvolumes:
        # On older systems like SLE11 findmnt does not know about FSROOT
        # see https://github.com/rear/rear/issues/883
        # therefore use by default the traditional mount command
        read_mounted_btrfs_subvolumes_command="mount -t btrfs | cut -d ' ' -f 1,3,6"
        # and use findmnt only if "findmnt -o FSROOT" works:
        # Use the (deprecated) "findmnt -m" to avoid issues
        # as in https://github.com/rear/rear/issues/882
        # FIXME: Replace using the deprecated '-m' option with a future proof solution.

        if test -x "$findmnt_command" && $findmnt_command -mnrv -o FSROOT -t btrfs &>/dev/null ; then
            read_mounted_btrfs_subvolumes_command="$findmnt_command -mnrv -o SOURCE,TARGET,OPTIONS,FSROOT -t btrfs"
            findmnt_FSROOT_works="yes"
        fi
        while read device subvolume_mountpoint mount_options btrfs_subvolume_path junk ; do
            # Work with the persistent dev name:
            # Address the fact than dm-XX may be different disk in the recovery environment.
            # See https://github.com/rear/rear/pull/695
            device=$( get_device_mapping $device )
            device=$( get_device_name $device )
            # Output btrfsmountedsubvol entries:
            if test -n "$device" -a -n "$subvolume_mountpoint" ; then
                if test -z "$btrfsmountedsubvol_entry_exists" ; then
                    # Output header only once:
                    btrfsmountedsubvol_entry_exists="yes"
                    echo "# All mounted btrfs subvolumes (including mounted btrfs default subvolumes and mounted btrfs snapshot subvolumes)."
                    echo "# Mounted btrfs snapshot subvolumes are autoexcluded."
                    if test "$findmnt_FSROOT_works" ; then
                        echo "# Determined by the findmnt command that shows the mounted btrfs_subvolume_path."
                        echo "# Format: btrfsmountedsubvol <device> <subvolume_mountpoint> <mount_options> <btrfs_subvolume_path>"
                    else
                        echo "# Determined by the traditional mount command that cannot show the mounted btrfs_subvolume_path."
                        echo "# The mounted btrfs_subvolume_path is read from /etc/fstab if it can be found there."
                        echo "# Without btrfs_subvolume_path the btrfs subvolume cannot be mounted during system recovery."
                        echo "# Format: btrfsmountedsubvol <device> <subvolume_mountpoint> <mount_options> [<btrfs_subvolume_path>]"
                    fi
                fi
                # Remove parenthesis (from the traditional mount command output) from the list of mount options:
                mount_options=${mount_options#(}
                mount_options=${mount_options%)}
                if test -z "$btrfs_subvolume_path" ; then
                    # When btrfs_subvolume_path is empty (in particular when the traditional mount command is used)
                    # try to find the mountpoint in /etc/fstab and try to read the subvol=... option value if exists
                    # (using subvolid=... can fail because the subvolume ID can be different during system recovery).
                    # Because both "mount ... -o subvol=/path/to/subvolume" and "mount ... -o subvol=path/to/subvolume" work
                    # the subvolume path can be specified with or without leading '/'.
                    # Avoid SC1087 by using ${subvolume_mountpoint} with curly brackets because
                    # we need the subsequent square brackets literally (subvolume_mountpoint is a string, not an array):
                    btrfs_subvolume_path=$( grep -E "[[:space:]]${subvolume_mountpoint}[[:space:]]+btrfs[[:space:]]" /etc/fstab \
                                            | grep -E -v '^[[:space:]]*#' \
                                            | grep -o 'subvol=[^ ]*' | cut -s -d '=' -f 2 )
                fi
                # Remove leading '/' from btrfs_subvolume_path (except it is only '/') to have same syntax for all entries and
                # without leading '/' is more clear that it is not an absolute path in the currently mounted tree of filesystems
                # instead the subvolume path is relative to the toplevel/root subvolume of the particular btrfs filesystem
                # (i.e. a subvolume path is an absolute path in the particular btrfs filesystem)
                # see https://btrfs.wiki.kernel.org/index.php/Mount_options
                test "/" != "$btrfs_subvolume_path" && btrfs_subvolume_path=${btrfs_subvolume_path#/}

                # Finally, test whether the btrfs subvolume listed as mounted actually exists. A running docker
                # daemon apparently can convince the system to list a non-existing btrfs volume as mounted.
                # See https://github.com/rear/rear/issues/1496
                if btrfs_snapshot_subvolume_exists "$subvolume_mountpoint" "$btrfs_subvolume_path"; then
                    # Exclude mounted snapshot subvolumes
                    echo "#btrfsmountedsubvol $device $subvolume_mountpoint $mount_options $btrfs_subvolume_path"
                elif btrfs_subvolume_exists "$subvolume_mountpoint" "$btrfs_subvolume_path"; then
                    echo "btrfsmountedsubvol $device $subvolume_mountpoint $mount_options $btrfs_subvolume_path"
                else
                    LogPrintError "Ignoring non-existing btrfs subvolume listed as mounted: $subvolume_mountpoint"
                    echo "# Ignoring non-existing btrfs subvolume listed as mounted:"
                    echo "#btrfsmountedsubvol $device $subvolume_mountpoint $mount_options $btrfs_subvolume_path"
                fi
            fi
        done < <( eval $read_mounted_btrfs_subvolumes_command )
        ########################################
        # No copy on write attributes of mounted btrfs subvolumes:
        echo "# Mounted btrfs subvolumes that have the 'no copy on write' attribute set."
        echo "# Format: btrfsnocopyonwrite <btrfs_subvolume_path>"
        lsattr_command="$( type -P lsattr )"
        # On older systems like SLE11 findmnt does not know about FSROOT (see above)
        # therefore test if findmnt_FSROOT_works was set above:
        # Use the (deprecated) "findmnt -m" to avoid issues
        # as in https://github.com/rear/rear/issues/882
        # FIXME: Replace using the deprecated '-m' option with a future proof solution.
        if test -x "$lsattr_command" -a -x "$findmnt_command" -a "$findmnt_FSROOT_works" ; then
            for subvolume_mountpoint in $( $findmnt_command -mnrv -o TARGET -t btrfs ) ; do
                # The 'no copy on write' attribute is shown as 'C' in the lsattr output (see "man chattr"):
                if $lsattr_command -d $subvolume_mountpoint | cut -d ' ' -f 1 | grep -q 'C' ; then
                    btrfs_subvolume_path=$( $findmnt_command -mnrv -o FSROOT $subvolume_mountpoint )
                    # Remove leading '/' from btrfs_subvolume_path (except it is only '/') to have same syntax for all entries and
                    # without leading '/' is more clear that it is not an absolute path in the currently mounted tree of filesystems
                    # instead the subvolume path is relative to the toplevel/root subvolume of the particular btrfs filesystem
                    # (i.e. a subvolume path is an absolute path in the particular btrfs filesystem)
                    # see https://btrfs.wiki.kernel.org/index.php/Mount_options
                    test "/" != "$btrfs_subvolume_path" && btrfs_subvolume_path=${btrfs_subvolume_path#/}
                    if test -n "$btrfs_subvolume_path" ; then
                        # Add the following binaries to the rescue image in order to be able to change required attrs upon recovery.
                        # See conf/examples/SLE12-SP2-btrfs-example.conf and https://github.com/rear/rear/issues/2927
                        REQUIRED_PROGS+=( chattr )
                        PROGS+=( lsattr )
                        echo "btrfsnocopyonwrite $btrfs_subvolume_path"
                    else
                        echo "# $subvolume_mountpoint has the 'no copy on write' attribute set but $findmnt_command does not show its btrfs subvolume path"
                    fi
                fi
            done
        else
            echo "# Attributes cannot be determined because no executable 'lsattr' and/or 'findmnt' command(s) found that supports 'FSROOT'."
        fi

        if test "$btrfs_subvolume_sles_setup_devices" ; then
            # Save the updated BTRFS_SUBVOLUME_SLES_SETUP array variable that is needed in recover mode into the rescue.conf file:
            cat - <<EOF >> "$ROOTFS_DIR/etc/rear/rescue.conf"
# During "rear mkbackup/mkrescue" via usr/share/rear/layout/save/GNU/Linux/230_filesystem_layout.sh
# all btrfs filesystem device nodes with SLES12-SP1 (and later) btrfs subvolumes setup needed
# were appended to the BTRFS_SUBVOLUME_SLES_SETUP array (unless such a device was already in that array)
# to enforce during "rear recover" btrfs_subvolumes_setup_SLES() gets called to setup that btrfs filesystem
# (see the usr/share/rear/layout/prepare/GNU/Linux/133_include_mount_filesystem_code.sh script):
BTRFS_SUBVOLUME_SLES_SETUP+=( $btrfs_subvolume_sles_setup_devices )

EOF
            # The rescue.conf file is sourced last by usr/sbin/rear i.e. after site.conf and local.conf
            # so that the settings in rescue.conf have highest priority.
            LogPrint "Added $btrfs_subvolume_sles_setup_devices to BTRFS_SUBVOLUME_SLES_SETUP in $ROOTFS_DIR/etc/rear/rescue.conf"
        fi


    # End btrfs subvolume layout if a btrfs filesystem exists:
    fi

} 1>>$DISKLAYOUT_FILE
# End of group command that appends its stdout to DISKLAYOUT_FILE

# mkfs is required in the recovery system if disklayout.conf contains at least one 'fs' entry
# see the create_fs function in layout/prepare/GNU/Linux/130_include_filesystem_code.sh
# what program calls are written to diskrestore.sh
# cf. https://github.com/rear/rear/issues/1963
grep -q '^fs ' $DISKLAYOUT_FILE && REQUIRED_PROGS+=( mkfs )
# Other filesystem creating tools are required in the recovery system
# depending on which filesystem types entries exist in disklayout.conf
# (see above supported_filesystems="ext2,ext3,ext4,vfat,xfs,reiserfs,btrfs"):
required_mkfs_tools=""
for filesystem_type in $( echo $supported_filesystems | tr ',' ' ' ) ; do
    grep -q "^fs .* $filesystem_type " $DISKLAYOUT_FILE && required_mkfs_tools+=" mkfs.$filesystem_type"
done
# Remove duplicates because in disklayout.conf there can be many entries with same filesystem type:
required_mkfs_tools="$( echo $required_mkfs_tools | tr ' ' '\n' | sort -u | tr '\n' ' ' )"
REQUIRED_PROGS+=( $required_mkfs_tools )
# mke2fs is also required in the recovery system if any 'mkfs.ext*' filesystem creating tool is required
# and tune2fs or tune4fs is used to set tunable filesystem parameters on ext2/ext3/ext4
# cf. above how $tunefs is set to tune2fs or tune4fs inside the subshell
# i.e. $tunefs is not set here so REQUIRED_PROGS+=( $tunefs ) would do nothing
# but tune2fs and tune4fs get included via PROGS in conf/GNU/Linux.conf which should be sufficient:
echo $required_mkfs_tools | grep -q 'mkfs.ext' && REQUIRED_PROGS+=( mke2fs )
# xfs_admin is also required in the recovery system if 'mkfs.xfs' is required:
echo $required_mkfs_tools | grep -q 'mkfs.xfs' && REQUIRED_PROGS+=( xfs_admin )
# reiserfstune is also required in the recovery system if 'mkfs.reiserfs' is required:
echo $required_mkfs_tools | grep -q 'mkfs.reiserfs' && REQUIRED_PROGS+=( reiserfstune )
# btrfs is also required in the recovery system if 'mkfs.btrfs' is required
# cf. what prepare/GNU/Linux/130_include_mount_subvolumes_code.sh writes to diskrestore.sh
echo $required_mkfs_tools | grep -q 'mkfs.btrfs' && REQUIRED_PROGS+=( btrfs )

Log "End saving filesystem layout"