Skip to content

Instantly share code, notes, and snippets.

@Thermionix
Created May 17, 2012 01:58
Show Gist options
  • Save Thermionix/2715615 to your computer and use it in GitHub Desktop.
Save Thermionix/2715615 to your computer and use it in GitHub Desktop.
usb hdd sync via udev & rsync
# /etc/udev/rules.d/local.rules
# ADD rule: if we have a valid ID_FS_LABEL_ENC, and it's USB, mkdir and mount
ENV{ID_FS_LABEL_ENC}=="?*", ACTION=="add", SUBSYSTEMS=="usb", \
RUN+="/usr/local/sbin/udev-automounter.sh %k"
# /usr/local/sbin/media.exclude
#
*.donotsync
Mythbusters/
Conan/
Firefly/DVD Extras/
# /usr/local/sbin/media.include
#
# Include only these subdirs of /media/md0
series/
music/
movies/
#!/bin/sh
# /usr/local/sbin/rsync_media.sh
#
MOUNT=$1
if [ -z "$MOUNT" ]; then
exit 1 # exit if no input
fi
MEDIA="/media/md0"
EXCLUDE="--exclude-from=/usr/local/sbin/media.exclude"
INCLUDE="--files-from=/usr/local/sbin/media.include"
RSYNC_CMD="rsync -rptgo --delete --ignore-existing --delete-excluded --stats" # -v -n
# check $MEDIA
# check exclude and include
$RSYNC_CMD $INCLUDE $EXCLUDE $MEDIA $MOUNT
#!/bin/bash
# /usr/local/sbin/suspend-usb-device.sh
#
# suspend-usb-device: an easy-to-use script to properly put an USB
# device into suspend mode that can then be unplugged safely
#
# Copyright (C) 2009, Yan Li <elliot.li.tech@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# To reach the auther, please write an email to the address as stated
# above.
# ACKNOWLEDGEMENTS:
# Christian Schmitt <chris@ilovelinux.de> for firewire supporting
# David <d.tonhofer@m-plify.com> for improving parent device
# search and verbose output message
usage()
{
cat<<EOF
suspend-usb-device Copyright (C) 2009 Yan Li <elliot.li.tech@gmail.com>
This script is designed to properly put an USB device into suspend
mode that can then be unplugged safely. It sends a SYNCHRONIZE CACHE
command followed by a START-STOP command (if the device supports it),
unbinds the device from the driver and then suspends the USB
port. After that you can disconnect your USB device safely.
usage:
$0 [options] dev
sample:
$0 /dev/sde
options:
-l show the device and USB bus ID only
-h print this usage
-v verbose
This program comes with ABSOLUTELY NO WARRANTY. This is free
software, and you are welcome to redistribute it under certain
conditions; for details please read the licese at the beginning of the
source code file.
EOF
}
set -e -u
SHOW_DEVICE_ONLY=0
VERBOSE=0
while getopts "vlh" opt; do
case "$opt" in
h)
usage
exit 2
;;
l)
SHOW_DEVICE_ONLY=1
;;
v)
VERBOSE=1
;;
?)
echo
usage
exit 2
;;
esac
done
DEV_NAME=${!OPTIND:-}
if [ -z ${DEV_NAME} ]; then
usage
exit 2
fi
# mount checking
if mount | grep "^${DEV_NAME}[[:digit:]]* "; then
1>&2 echo
1>&2 echo "the above disk or partition is still mounted, can't suspend device"
1>&2 echo "unmount it first using umount"
exit 1
fi
# looking for the parent of the device with type "usb-storage:usb", it
# is the grand-parent device of the SCSI host, and it's devpath is
# like
# /devices/pci0000:00/0000:00:1d.7/usb5/5-8 (or /fw5/fw5-8 for firewire devices)
# without an USB hub, the device path looks like:
# /devices/pci0000:00/0000:00:1d.7/usb2/2-1/2-1:1.0/host5/target5:0:0/5:0:0:0
# here the grand-parent of host5 is 2-1
# when there's a USB HUB, the device path is like:
# /devices/pci0000:00/0000:00:1d.0/usb5/5-2/5-2.2/5-2.2:1.0/host4/target4:0:0/4:0:0:0
# and the grand-parent of host4 is 5-2.2
DEVICE=$(udevadm info --query=path --name=${DEV_NAME} --attribute-walk | \
egrep "looking at parent device" | head -1 | \
sed -e "s/.*looking at parent device '\(\/devices\/.*\)\/.*\/host.*/\1/g")
if [ -z $DEVICE ]; then
1>&2 echo "cannot find appropriate parent USB/Firewire device, "
1>&2 echo "perhaps ${DEV_NAME} is not an USB/Firewire device?"
exit 1
fi
# the trailing basename of ${DEVICE} is DEV_BUS_ID ("5-8" in the
# sample above)
DEV_BUS_ID=${DEVICE##*/}
[[ $VERBOSE == 1 ]] && echo "Found device $DEVICE associated to $DEV_NAME; USB bus id is $DEV_BUS_ID"
if [ ${SHOW_DEVICE_ONLY} -eq 1 ]; then
echo Device: ${DEVICE}
echo Bus ID: ${DEV_BUS_ID}
exit 0
fi
# flush all buffers
sync
# root check
if [ `id -u` -ne 0 ]; then
1>&2 echo error, must be run as root, exiting...
exit 1
fi
# send SCSI sync command, some devices don't support this so we just
# ignore errors with "|| true"
[[ $VERBOSE == 1 ]] && echo "Syncing device $DEV_NAME"
sdparm --command=sync "$DEV_NAME" >/dev/null || true
# send SCSI stop command
[[ $VERBOSE == 1 ]] && echo "Stopping device $DEV_NAME"
sdparm --command=stop "$DEV_NAME" >/dev/null
# unbind it; if this yields "no such device", we are trying to unbind the wrong device
[[ $VERBOSE == 1 ]] && echo "Unbinding device $DEV_BUS_ID"
if [[ "${DEV_BUS_ID}" == fw* ]]
then
echo -n "${DEV_BUS_ID}" > /sys/bus/firewire/drivers/sbp2/unbind
else
echo -n "${DEV_BUS_ID}" > /sys/bus/usb/drivers/usb/unbind
# suspend it if it's an USB device (we have no way to suspend a
# firewire device yet)
# check if CONFIG_USB_SUSPEND is enabled
[[ $VERBOSE == 1 ]] && echo "Checking whether $DEVICE can be suspended"
AUTOSUSPEND_FILE=/sys${DEVICE}/power/autosuspend
POWER_LEVEL_FILE=/sys${DEVICE}/power/level
if [ ! -f "$POWER_LEVEL_FILE" ]; then
1>&2 cat<<EOF
It's safe to remove the USB device now but better can be done. The
power level control file $POWER_LEVEL_FILE
doesn't exist on the system so I have no way to put the USB device
into suspend mode, perhaps you don't have CONFIG_USB_SUSPEND enabled
in your running kernel.
EOF
exit 3
fi
[[ $VERBOSE == 1 ]] && echo "Suspending $DEVICE by writing to $POWER_LEVEL_FILE"
echo "0" > "$AUTOSUSPEND_FILE"
echo "auto" > "$POWER_LEVEL_FILE"
fi
#!/bin/sh
# /usr/local/sbin/udev-automounter.sh
#
# USAGE: usb-automounter.sh DEVICE
# DEVICE is the actual device node at /dev/DEVICE
/usr/local/sbin/udev-mount.sh ${1} &
#!/bin/sh
# /usr/local/sbin/udev-mount.sh
#
# USAGE: udev-auto-mount.sh DEVICE
# DEVICE is the actual device node at /dev/DEVICE
DEVICE=$1
if [ -z "$DEVICE" ]; then
exit 1 # exit if no input
fi
UUID_LIST="/usr/local/sbin/uuid.list" # a csv of uuid's and a script to exec for that drive
UMNT_SH="/usr/local/sbin/udev-unmounter.sh"
SUSPEND_SH="/usr/local/sbin/suspend-usb-device.sh -v"
log_file=/var/log/rsync_udev.log
exec > $log_file 2>&1
DEV_ID=`blkid -c /dev/null -o value -s UUID /dev/$DEVICE`
if [ -z "$DEV_ID" ]; then
echo "error: $DEVICE did not return a UUID"
exit 1 # exit if no input
fi
echo "info: USB Drive attached with uuid ${DEV_ID}"
if [ ! -e "${UUID_LIST}" ]; then
echo "error: ${UUID_LIST} missing"
exit 1
fi
if [ ! -e "${UMNT_SH}" ]; then
echo "error: ${UMNT_SH} missing"
exit 1
fi
while IFS=, read uuid script
do
if [ "$uuid" = "$DEV_ID" ]; then
echo "info: ${uuid} recognised, will exec ${script}"
DEV_SCRIPT=$script
break
fi
done < $UUID_LIST
if [ -z "${DEV_SCRIPT}" ]; then
echo "warning: drive ${DEV_ID} not recognised in ${UUID_LIST}"
echo "terminating process"
exit 0
fi
if ( grep -q ${DEVICE} /etc/mtab ); then
echo "error: seems /dev/${DEVICE} is already mounted"
exit 1
fi
DEV_FS=`blkid -c /dev/null -o value -s TYPE /dev/$DEVICE`
if [ -z "$DEV_FS" ]; then
echo "error: /dev/${DEVICE} ${DEV_ID} fs_type empty!"
exit 1
fi
echo "info: ${DEV_ID} filesystem type is ${DEV_FS}"
MNT="/media/${DEV_ID}"
if [ ! -e "${MNT}" ]; then
mkdir "${MNT}"
# If expecting thumbdrives, you probably want
# mount -t auto -o sync,noatime [...]
#
# If drive is VFAT/NFTS, this mounts the filesystem such that all files
# are owned by a std user instead of by root. Change to your user's UID
# (listed in /etc/passwd). You may also want "gid=1000" and/or "umask=022", eg:
# mount -t auto -o uid=1000,gid=1000 [...]
#
# ext2/3/4 don't like uid option
case "$DEV_FS" in
vfat) mount -t vfat -o sync,noatime,uid=1000 /dev/${DEVICE} "${MNT}"
;;
ntfs) mount -t ntfs -o sync,noatime,uid=1000,locale=en_US.UTF-8 /dev/${DEVICE} "${MNT}"
;;
ext*) mount -t auto -o sync,noatime /dev/${DEVICE} "${MNT}"
;;
esac
sleep 2
if ( grep -q ${DEVICE} /etc/mtab ); then
echo "info: ${DEV_ID} mounted at ${MNT}"
echo "info: exec ${DEV_SCRIPT}"
$DEV_SCRIPT $MNT
echo
echo "info: umount ${MNT}"
$UMNT_SH $MNT
echo "info: exec ${SUSPEND_SH}"
$SUSPEND_SH /dev/${DEVICE}
cat $log_file | mail -s "${MNT} backup completed" --email--@gmail.com
# append new log to another logfile
exit 0
fi
fi
exit 1
#!/bin/sh
# /usr/local/sbin/udev-unmounter.sh
#
# USAGE: udev-unmounter.sh MOUNTPT
# MOUNTPT is a mountpoint we want to unmount and delete.
MOUNTPT="$1"
if [ -z "$MOUNTPT" ]; then
exit 1
fi
if [ -e "${MOUNTPT}" ]; then
umount -l "${MOUNTPT}" && rmdir "${MOUNTPT}" && exit 0
echo "error: ${MOUNTPT} failed to unmount."
exit 1
fi
echo "error: ${MOUNTPT} does not exist"
exit 1
# /usr/local/sbin/uuid.list
#
# Define scripts to run for specific drives;
08B4A427B4A418E8,/usr/local/sbin/rsync_media.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment