Files
webgui/sbin/mover

220 lines
5.8 KiB
Bash
Executable File

#!/bin/bash
#Copyright 2005-2024, Lime Technology, Inc.
#License: GPLv2 only
# This is the 'mover' script used for moving files between pools and/or the main unRAID array.
# It is typically invoked via cron.
# The script is set up so that hidden directories (i.e., directory names beginning with a '.'
# character) at the topmost level of a pool or an array disk are not moved. This behavior
# can be turned off by uncommenting the following line:
# shopt -s dotglob
# Files at the top level of a pool or an array disk are never moved.
# The 'find' command generates a list of all files and directories of a share.
# For each file, if the file is not "in use" by any process (as detected by 'in_use' command),
# then the file is moved, and upon success, deleted from the source disk. If the file already
# exists on the target, it is not moved and the source is not deleted. All meta-data of moved
# files/directories is preserved: permissions, ownership, extended attributes, and access/modified
# timestamps.
# If an error occurs in copying a file, the partial file, if present, is deleted and the
# operation continues on to the next file.
PIDFILE="/var/run/mover.pid"
CFGPATH="/boot/config/shares"
DEBUGGING=""
move() {
find "$1" -depth 2>/dev/null | /usr/libexec/unraid/move $2 $DEBUGGING
# second pass to clean up leftover empty directories
find "$1" -depth -type d 2>/dev/null | /usr/libexec/unraid/move $2 $DEBUGGING
}
start() {
if [ -f $PIDFILE ]; then
if ps h $(cat $PIDFILE) | grep mover ; then
echo "mover: already running"
exit 1
fi
fi
echo $$ >/var/run/mover.pid
echo "mover: started"
shopt -s nullglob
for SHARECFG in $CFGPATH/*.cfg ; do
SHARE=$(basename "$SHARECFG" .cfg)
source <(fromdos < "$SHARECFG")
# maybe move from primary to secondary
if [[ $shareUseCache = yes && -n $shareCachePool && -d "/mnt/$shareCachePool/$SHARE" ]]; then
if [[ -n $shareCachePool2 ]]; then
# secondary is a pool
if [[ -d "/mnt/$shareCachePool2" ]]; then
move "/mnt/$shareCachePool/$SHARE"
fi
else
# secondary is the unRAID array, ensure at least one data disk is present
disks=(/mnt/disk[1-9]/ /mnt/disk[1-9][0-9]/)
if [[ ${#disks[@]} -gt 0 ]]; then
move "/mnt/$shareCachePool/$SHARE"
fi
fi
fi
# maybe move from secondary to primary
if [[ $shareUseCache = prefer ]]; then
if [[ -n $shareCachePool2 ]]; then
# secondary is a pool (primary is also a pool)
if [[ -d "/mnt/$shareCachePool2/$SHARE" && -n $shareCachePool && -d "/mnt/$shareCachePool" ]]; then
move "/mnt/$shareCachePool2/$SHARE"
fi
else
# secondary is the unRAID array
if [[ -n $shareCachePool && -d "/mnt/$shareCachePool" ]]; then
for SHAREPATH in /mnt/disk[1-9]/"$SHARE"/ /mnt/disk[1-9][0-9]/"$SHARE"/ ; do
move "$SHAREPATH"
done
fi
fi
fi
done
# maybe empty an array disk
EMPTYING=$(grep '^shareUserEmptying=' /boot/config/share.cfg | cut -d'"' -f2)
IFS=',' read -ra arr <<< "$EMPTYING"
for DISK in "${arr[@]}"; do
# we can only empty share directories
for SHAREPATH in /mnt/$DISK/* ; do
SHARE=$(basename "$SHAREPATH")
if [[ -d "$SHAREPATH" && -f "$CFGPATH/$SHARE.cfg" ]]; then
move "$SHAREPATH" "-e"
fi
done
# output list of files which could not be moved
# use 'find' in case huge number of files left in /mnt/$DISK
count=$(find /mnt/$DISK -mindepth 1 | wc -l)
if [ "$count" -gt 0 ]; then
find /mnt/$DISK -mindepth 1 -depth -printf 'move: %p Not moved\n' | head -n 100
if [ "$count" -gt 100 ]; then
echo "[output truncated to first 100 entries]"
fi
fi
done
rm -f $PIDFILE
echo "mover: finished"
}
empty() {
DISK="$1"
if [ -f $PIDFILE ]; then
if ps h $(cat $PIDFILE) | grep mover ; then
echo "mover: already running"
exit 1
fi
fi
echo $$ >/var/run/mover.pid
echo "mover: started"
shopt -s nullglob
# we can only empty share directories
for SHAREPATH in /mnt/$DISK/* ; do
SHARE=$(basename "$SHAREPATH")
if [[ -d "$SHAREPATH" && -f "$CFGPATH/$SHARE.cfg" ]]; then
move "$SHAREPATH" "-e"
fi
done
# output list of files which could not be moved
# use 'find' in case huge number of files left in /mnt/$DISK
count=$(find /mnt/$DISK -mindepth 1 | wc -l)
if [ "$count" -gt 0 ]; then
find /mnt/$DISK -mindepth 1 -depth -printf 'move: %p Not moved\n' | head -n 100
if [ "$count" -gt 100 ]; then
echo "[output truncated to first 100 entries]"
fi
fi
rm -f $PIDFILE
echo "mover: finished"
}
killtree() {
local pid=$1 child
for child in $(pgrep --ns $$ -P $pid); do
killtree $child
done
[ $pid -ne $$ ] && kill -TERM $pid
}
stop() {
if [ ! -f $PIDFILE ]; then
echo "mover: not running"
exit 0
fi
killtree $(cat $PIDFILE)
sleep 2
rm -f $PIDFILE
echo "mover: stopped"
}
# display usage and then exit
usage() {
echo "Usage: $0 start [-e <disk_name>]"
echo " $0 stop|status"
echo " <disk_name> must match pattern 'disk[0-9]*' and /mnt/<disk_name> must be a mountpoint"
exit 1
}
# validate disk name
validate_disk() {
if [[ ! "$1" =~ ^disk[0-9]+$ ]]; then
echo "Error: <disk_name> must match pattern 'disk[0-9]+$'"
usage
fi
if ! mountpoint --nofollow /mnt/$1 > /dev/null 2>&1; then
echo "Error: nothing mounted at /mnt/$1"
usage
fi
}
if [ "$#" -lt 1 ]; then
usage
exit 1
fi
case $1 in
start)
if [ -z "$2" ]; then
start
elif [ "$2" == "-e" ]; then
if [ -z "$3" ]; then
usage
else
validate_disk "$3"
empty "$3"
fi
else
usage
fi
;;
stop)
stop
;;
status)
[ -f $PIDFILE ]
;;
*)
usage
;;
esac