Linux - Hardware This forum is for Hardware issues.
Having trouble installing a piece of hardware? Want to know if that peripheral is compatible with Linux? |
| Notices |
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
Are you new to LinuxQuestions.org? Visit the following links:
Site Howto |
Site FAQ |
Sitemap |
Register Now
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
 |
GNU/Linux Basic Guide
This 255-page guide will provide you with the keys to understand the philosophy of free software, teach you how to use and handle it, and give you the tools required to move easily in the world of GNU/Linux. Many users and administrators will be taking their first steps with this GNU/Linux Basic guide and it will show you how to approach and solve the problems you encounter.
Click Here to receive this Complete Guide absolutely free. |
|
 |
10-19-2007, 11:58 PM
|
#1
|
|
Senior Member
Registered: Jan 2005
Location: Manalapan, NJ
Distribution: Fedora x86 and x86_64, Debian PPC and ARM, Android
Posts: 4,591
|
Howto: Spin down external (USB/Firewire) hard drives on idle
Most external hard drives will respond to an sdparm command to stop the drive. However, it's inconvenient to run the command(s) manually every time, especially if the drives are used off-hours by cron jobs. I created the attached script, idleDrive, which will let you spin down external drives automatically after they go idle. To use the script, add a cron job (usually on root) to run idleDrive, and specify the drive to be idled by either label, vendor or UUID. For example, to spin down a drive with the label "Videos" after five minutes of idle, you would add the cron job (assuming idleDrive is in /usr/local/bin/):
Code:
*/5 * * * * /usr/local/bin/idleDrive label=Videos &>/dev/null
You can easily specify multiple drives, selected with different criteria, using different time intervals:
Code:
*/5 * * * * /usr/local/bin/idleDrive label=Videos &>/dev/null
*/15 * * * * /usr/local/bin/idleDrive uuid=104355ff-685e-4fa9-8869-bfa82d2fb9b6 &>/dev/null
*/30 * * * * /usr/local/bin/idleDrive vendor=Maxtor &>/dev/null
The script itself follows. I use this on five different drives across three system. I've read that some drives will not respond to the spin-down commands. Some USB/IEEE1394 combo drives will only respond on one interface. You will need the sdparm command installed; it should be available from your distributions repositories. Hopefully, this will work for you:
Code:
#!/bin/bash
#-------------------------------------------------------------------------------
#
# idleDrive
#
# Author: Mace Moneta
# Version: 1.2
# Created: 10/12/2007
# Modified: 10/18/2007
#
# Description: Spin down the drive when it goes idle
#
# Options:
#
# vendor=vendor-name Example: vendor=Maxtor
# uuid=uuid-string Example: uuid=104355ff-685e-4fa9-8869-bfa82d2fb9b6
# label=volume-label Example: label=ExternalDisk
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# ************************** Variables **************************
#-------------------------------------------------------------------------------
PARAM=$1
OPTION=${PARAM%%=*}
VALUE=${PARAM##*=}
LOCK="/dev/shm/idleDrive-${VALUE}.lock"
STATE="/dev/shm/idleDrive-${VALUE}.state"
DOWN="/dev/shm/idleDrive-${VALUE}.down"
#-------------------------------------------------------------------------------
# ************************* Subroutines *************************
#-------------------------------------------------------------------------------
#--- FUNCTION ----------------------------------------------------------------
# Name: log
# Description: Log a message to the syslog and terminal
# Parameters: The message to log
# Returns: Nothing
#-------------------------------------------------------------------------------
function log () {
PREFIX="idleDrive.info"
PREFIXLEN=${#PREFIX}
PREFIXLEN=$((10#$PREFIXLEN+2))
DASHES="---------------------------------------------------------------------------------"
MSG=$1
MSGLEN=${#MSG}
MSGLEN=$((10#$MSGLEN+$PREFIXLEN))
DASH=${DASHES:0:$MSGLEN}
echo
echo "$DASH"
/usr/bin/logger -s -t $PREFIX "$MSG"
echo "$DASH"
}
#--- FUNCTION ----------------------------------------------------------------
# Name: CLEANUP
# Description: Clear the lock and exit
# Parameters: None
# Returns: Nothing
#-------------------------------------------------------------------------------
function CLEANUP () {
/bin/rm -f $LOCK
log "Exiting on interrupt"
exit
}
trap CLEANUP INT
trap CLEANUP HUP
trap CLEANUP QUIT
trap CLEANUP USR1
trap CLEANUP TERM
#-------------------------------------------------------------------------------
# ************************* Mainline ****************************
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Validate the parameters and
# Identify the device that the drive is known to the system as.
#-------------------------------------------------------------------------------
case "$OPTION" in
"vendor" )
for drive in `/bin/ls /sys/block/sd?/device/vendor`
do
if [ "`/bin/cat $drive`" == "$VALUE" ]
then
drive=${drive##/sys/block/}
drive="/dev/${drive%%/device/vendor}"
disk=${drive##/dev/}
break
else
exit 0
fi
done ;;
"uuid" )
drive=`/bin/ls -l /dev/disk/by-uuid/ | \
/bin/grep "$VALUE" | \
/bin/awk '{print $11}'`
drive=${drive##*/}
disk=${drive:0:3}
drive="/dev/${disk:0:3}"
if [ "$disk" == "" ]
then
exit 0
fi ;;
"label" )
drive=`/bin/ls -l /dev/disk/by-label/ | \
/bin/grep "$VALUE" | \
/bin/awk '{print $11}'`
drive=${drive##*/}
disk=${drive:0:3}
drive="/dev/${disk:0:3}"
if [ "$disk" == "" ]
then
exit 0
fi ;;
* )
log "Invalid option: $OPTION"
log "Specify vendor, uuid or label"
exit 1 ;;
esac
#-------------------------------------------------------------------------------
# Serialize execution
#-------------------------------------------------------------------------------
if [ -f $LOCK ]
then
log "Already in progress for $drive ($VALUE)"
exit 1
fi
/bin/touch $LOCK
#-------------------------------------------------------------------------------
# Check I/O stats to see if the drive has been idle
#-------------------------------------------------------------------------------
state=`/bin/grep " $disk " /proc/diskstats`
if [ -f $STATE ]
then
previousState=`/bin/cat $STATE`
else
log "Initializing state for $drive ($VALUE)"
echo "$state" > $STATE
/bin/rm -f $LOCK
exit 0
fi
#-------------------------------------------------------------------------------
# If the I/O stats haven't changed, the drive is idle
#-------------------------------------------------------------------------------
if [ "$state" == "$previousState" ] && [ ! -f $DOWN ]
then
log "Spin down drive $drive ($VALUE)"
/bin/sync
/usr/bin/sdparm --flexible --command=sync $drive &>/dev/null
/usr/bin/sdparm --flexible --command=stop $drive &>/dev/null
state=`/bin/grep " $disk " /proc/diskstats`
echo "$state" > $STATE
echo "down" > $DOWN
else
if [ "$state" != "$previousState" ]
then
/bin/rm -f $DOWN
echo "$state" > $STATE
fi
fi
#-------------------------------------------------------------------------------
# Done
#-------------------------------------------------------------------------------
/bin/rm -f $LOCK
exit 0
|
|
|
|
11-14-2007, 01:34 PM
|
#2
|
|
LQ Newbie
Registered: Nov 2007
Posts: 1
Rep:
|
some modifications for ubuntu
I'm running [k]ubuntu 7.10, and had to make a few small changes to get this to work on my system. First, awk is located in /usr/bin as opposed to /bin -- you can either create a symlink from /bin/awk to /usr/bin/awk or change the script to point to the right place for ubuntu. Second, for the uuid case, awk should check for the 10th field instead of the 11th. That is,
Code:
/bin/awk '{print $10}'`
Finally, the vendor matching doesn't seem to work for me. The else which results in exit should probably be moved outside of the for loop, after testing on $disk. As it is now, if the first drive doesn't equal the vendor, it'll exit. That is, it seems it should be:
Code:
"vendor" )
for drive in `/bin/ls /sys/block/sd?/device/vendor`
do
if [ "`/bin/cat $drive`" == "$VALUE" ]
then
drive=${drive##/sys/block/}
drive="/dev/${drive%%/device/vendor}"
disk=${drive##/dev/}
break
fi
done
if [ "$disk" == "" ]
then
log "found no drive for vendor $VALUE"
exit 0
fi ;;
Though I think even that will only match the last drive where the vendor is $VALUE. Even with that change, I can't get vendor matching to work for me. I am far from a shell scripting guru, so there probably are errors in my code.
I did not test the matching by label.
Other than that, the script works great for me. Thanks!
(Note, my whole modified version is below. This code is provided without warranty, including implied warranties. I abandon any copyright claim I may have in any of the modifications I have made into the public domain. The original author Mace Moneta, of course, still holds his copyright on the code.)
Code:
#!/bin/bash
#-------------------------------------------------------------------------------
#
# idleDrive
#
# Author: Mace Moneta
# Version: 1.2
# Created: 10/12/2007
# Modified: 10/18/2007
#
# Description: Spin down the drive when it goes idle
#
# Options:
#
# vendor=vendor-name Example: vendor=Maxtor
# uuid=uuid-string Example: uuid=104355ff-685e-4fa9-8869-bfa82d2fb9b6
# label=volume-label Example: label=ExternalDisk
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# ************************** Variables **************************
#-------------------------------------------------------------------------------
PARAM=$1
OPTION=${PARAM%%=*}
VALUE=${PARAM##*=}
LOCK="/dev/shm/idleDrive-${VALUE}.lock"
STATE="/dev/shm/idleDrive-${VALUE}.state"
DOWN="/dev/shm/idleDrive-${VALUE}.down"
MYAWK="/usr/bin/awk"
#-------------------------------------------------------------------------------
# ************************* Subroutines *************************
#-------------------------------------------------------------------------------
#--- FUNCTION ----------------------------------------------------------------
# Name: log
# Description: Log a message to the syslog and terminal
# Parameters: The message to log
# Returns: Nothing
#-------------------------------------------------------------------------------
function log () {
PREFIX="idleDrive.info"
PREFIXLEN=${#PREFIX}
PREFIXLEN=$((10#$PREFIXLEN+2))
DASHES="---------------------------------------------------------------------------------"
MSG=$1
MSGLEN=${#MSG}
MSGLEN=$((10#$MSGLEN+$PREFIXLEN))
DASH=${DASHES:0:$MSGLEN}
echo
echo "$DASH"
/usr/bin/logger -s -t $PREFIX "$MSG"
echo "$DASH"
}
#--- FUNCTION ----------------------------------------------------------------
# Name: CLEANUP
# Description: Clear the lock and exit
# Parameters: None
# Returns: Nothing
#-------------------------------------------------------------------------------
function CLEANUP () {
/bin/rm -f $LOCK
log "Exiting on interrupt"
exit
}
trap CLEANUP INT
trap CLEANUP HUP
trap CLEANUP QUIT
trap CLEANUP USR1
trap CLEANUP TERM
#-------------------------------------------------------------------------------
# ************************* Mainline ****************************
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Validate the parameters and
# Identify the device that the drive is known to the system as.
#-------------------------------------------------------------------------------
case "$OPTION" in
"vendor" )
for drive in `/bin/ls /sys/block/sd?/device/vendor`
do
if [ "`/bin/cat $drive`" == "$VALUE" ]
then
drive=${drive##/sys/block/}
drive="/dev/${drive%%/device/vendor}"
disk=${drive##/dev/}
break
fi
done
if [ "$disk" == "" ]
then
log "found no drive for vendor $VALUE"
exit 0
fi ;;
"uuid" )
drive=`/bin/ls -l /dev/disk/by-uuid/ | \
/bin/grep "$VALUE" | \
$MYAWK '{print $10}'`
drive=${drive##*/}
disk=${drive:0:3}
drive="/dev/${disk:0:3}"
if [ "$disk" == "" ]
then
log "found no drive for uuid $VALUE"
exit 0
fi ;;
"label" )
drive=`/bin/ls -l /dev/disk/by-label/ | \
/bin/grep "$VALUE" | \
$MYAWK '{print $11}'`
drive=${drive##*/}
disk=${drive:0:3}
drive="/dev/${disk:0:3}"
if [ "$disk" == "" ]
then
log "found no drive for label $VALUE"
exit 0
fi ;;
* )
log "Invalid option: $OPTION"
log "Specify vendor, uuid or label"
exit 1 ;;
esac
#-------------------------------------------------------------------------------
# Serialize execution
#-------------------------------------------------------------------------------
if [ -f $LOCK ]
then
log "Already in progress for $drive ($VALUE)"
exit 1
fi
/bin/touch $LOCK
#-------------------------------------------------------------------------------
# Check I/O stats to see if the drive has been idle
#-------------------------------------------------------------------------------
state=`/bin/grep " $disk " /proc/diskstats`
if [ -f $STATE ]
then
previousState=`/bin/cat $STATE`
else
log "Initializing state for $drive ($VALUE)"
echo "$state" > $STATE
/bin/rm -f $LOCK
exit 0
fi
#-------------------------------------------------------------------------------
# If the I/O stats haven't changed, the drive is idle
#-------------------------------------------------------------------------------
if [ "$state" == "$previousState" ] && [ ! -f $DOWN ]
then
log "Spin down drive $drive ($VALUE)"
/bin/sync
/usr/bin/sdparm --flexible --command=sync $drive &>/dev/null
/usr/bin/sdparm --flexible --command=stop $drive &>/dev/null
state=`/bin/grep " $disk " /proc/diskstats`
echo "$state" > $STATE
echo "down" > $DOWN
else
if [ "$state" != "$previousState" ]
then
/bin/rm -f $DOWN
echo "$state" > $STATE
fi
fi
#-------------------------------------------------------------------------------
# Done
#-------------------------------------------------------------------------------
/bin/rm -f $LOCK
exit 0
|
|
|
|
09-19-2010, 04:22 AM
|
#3
|
|
LQ Newbie
Registered: Aug 2010
Posts: 2
Rep:
|
Thank you macemoneta for the script and radar2k for the corrections. I'm using Ubuntu too and I had to modify the script a little.
Firstly, I had to call sdparm --command=stop twice the first time it (sdparm) is run as a workarround to a bug related with that utility (bug url: External disk won't spindown).
Another super tiny fix I've made is to use ls -l --full-time instead of ls -l because it appears that LC_TIME is not set correctly for processes spawned by cron. This must be the explanation as to why radar2k had to have awk check for the 10th field instead of the 11th.
Finally I removed the by vendor and by label disk suspension.
Here is my modified version:
Code:
#!/bin/bash
# Author: Mace Moneta
# Description: Spin down the drive when it goes idle
# Url: http://www.linuxquestions.org/questions/linux-hardware-18/howto-spin-down-external-usb-firewire-hard-drives-on-idle-593192/
# Modified By: 666f6f
PARAM=$1
OPTION=${PARAM%%=*}
VALUE=${PARAM##*=}
LOCK_FILE="/dev/shm/idle-drive-${VALUE}.lock"
STATE_FILE="/dev/shm/idle-drive-${VALUE}.state"
DOWN_FILE="/dev/shm/idle-drive-${VALUE}.down"
FIX="/dev/shm/idle-drive.fixed"
idle_drive_log() {
local PREFIX="idle-drive.info"
local PREFIXLEN=${#PREFIX}
local PREFIXLEN=$((10#$PREFIXLEN+2))
local MSG=$1
local MSGLEN=${#MSG}
local MSGLEN=$((10#$MSGLEN+$PREFIXLEN))
logger -s -t $PREFIX "$MSG"
}
idle_drive_cleanup() {
rm -f $LOCK_FILE
idle_drive_log "Exiting on interrupt."
exit
}
trap idle_drive_cleanup INT
trap idle_drive_cleanup HUP
trap idle_drive_cleanup QUIT
trap idle_drive_cleanup USR1
trap idle_drive_cleanup TERM
DRIVE=`ls -l --full-time /dev/disk/by-uuid/ | grep "$VALUE" | awk '{print $11}'`
DRIVE=${DRIVE##*/}
DISK=${DRIVE:0:3}
DRIVE="/dev/${DISK:0:3}"
echo $DRIVE
echo $DISK
if [ "$DISK" == "" ]; then
idle_drive_log 'Disk not found.'
exit 1
fi
if [ -f $LOCK_FILE ]; then
idle_drive_log "Already in progress for $DRIVE ($VALUE)."
exit 1
fi
touch $LOCK_FILE
# Check I/O stats to see if the drive has been idle
STATE=`grep " $DISK " /proc/diskstats`
if [ -f $STATE_FILE ]; then
STATE_PREVIOUS=`/bin/cat $STATE_FILE`
else
idle_drive_log "Initializing state for $DRIVE ($VALUE)."
echo "$STATE" > $STATE_FILE
rm -f $LOCK_FILE
exit 0
fi
# If the I/O stats haven't changed, the drive is idle
if [ "$STATE" == "$STATE_PREVIOUS" ] && [ ! -f $DOWN_FILE ]; then
idle_drive_log "Disk state unchanged and disk spinning. Spin down drive $DRIVE ($VALUE)"
sync
sdparm --flexible --command=sync $DRIVE &>/dev/null
sdparm --flexible --command=stop $DRIVE &>/dev/null
[ ! -f $FIX ] && sleep 5 && touch $FIX && sdparm --flexible --command=stop $DRIVE &>/dev/null
STATE=`grep " $DISK " /proc/diskstats`
echo "$STATE" > $STATE_FILE
touch $DOWN_FILE
else
if [ "$STATE" != "$STATE_PREVIOUS" ]; then
idle_drive_log "Disk state changed. Deleting $DOWN_FILE and refreshing state."
rm -f $DOWN_FILE
echo "$STATE" > $STATE_FILE
fi
fi
# Done
rm -f $LOCK_FILE
exit 0
Last edited by 666f6f; 09-19-2010 at 08:10 AM.
|
|
|
|
04-12-2011, 11:04 PM
|
#4
|
|
LQ Newbie
Registered: Jul 2010
Posts: 2
Rep:
|
I've been trying to spindown my external USB hard drive (solely used for backup purposes) without much success until I stumbled across this and other similar threads. What in the end worked for me, were the following set of commands. I should also add that I'm using Slackware 13.1 (64-bit) and external Buffalo HD enclosure (HD-HC320IU2 - 320GB replaced with a 2TB drive).
Identify the correct SCSI generic (sg) device id for your drive. I only have two, so it was easy to "eyeball" the one I was looking for. If you only have two drives, the second drive will most likely be /dev/sg1 (/dev/sg0 being the primary hdd):
Use sdparm to spin the drive down (substitute your sg device for /dev/sgx):
Code:
sdparm -C stop /dev/sgx
Use sdparm again to spin the drive back up:
Code:
sdparm -C sync /dev/sgx
Hope this helps someone out.
Vas
|
|
|
1 members found this post helpful.
|
04-13-2011, 01:11 PM
|
#5
|
|
LQ 5k Club
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Debian Squeeze (server), Slackware 13.37 (netbook), Slackware64 14.0 (desktop),
Posts: 8,358
|
Thanks MrVas, it certainly helped me out
It works on Slackware64 13.1 with an Hitachi "Portable DRIVE" a.k.a "SimpleDRIVE Mini" (500 GB USB HDD), model 0S00462 -- something I've been wanting to do since Feb 2010.
One surprising phenomenon: the USB HDD did not get much cooler than it is when spinning and idle. Presumably the electronics consume significant power.
Next project: how to determine the /dev/sg* name programatically starting with partition major and minor numbers from udev, preferably without having to install sg_utils.
|
|
|
|
12-04-2011, 09:18 AM
|
#6
|
|
LQ 5k Club
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Debian Squeeze (server), Slackware 13.37 (netbook), Slackware64 14.0 (desktop),
Posts: 8,358
|
Given an HDD device file name, for example /dev/sdc, the corresponding /dev/sg* can be found by:
Code:
root@CW8:~# ls -l /sys/block/sdc
lrwxrwxrwx 1 root root 0 2011-12-04 20:35 /sys/block/sdc -> ../devices/pci0000:00/0000:00:1c.5/0000:06:00.0/usb3/3-4/3-4:1.0/host13/target13:0:0/13:0:0:0/block/sdc
root@CW8:~# ls -l /sys/devices/pci0000:00/0000:00:1c.5/0000:06:00.0/usb3/3-4/3-4:1.0/host13/target13:0:0/13:0:0:0/scsi_generic
total 0
drwxr-xr-x 3 root root 0 2011-12-04 20:03 sg4
EDIT: sample bash to implement that technique robustly, using stat instead of ls (assumes $disk_dev_file is something like /dev/sdc):
Code:
# Spin down
# ~~~~~~~~~
msg I "spinning down $disk_dev_file ..."
sdX=${disk_dev_file##*/}
buf=$( stat --format %N /sys/block/$sdX 2>&1 )
buf=${buf#*../}
buf=${buf%/block/$sdX*}
buf=$( stat --format %n /sys/$buf/scsi_generic/* )
dev_sgX=/dev/${buf##*/}
msg D "SCSI generic device is $dev_sgX"
buf=$( sdparm --command stop --quiet $dev_sgX 2>&1 )
rc=$?
[[ $buf != '' || $rc -ne 0 ]] && msg W "sdparm return code: $rc, output: "$'\n'"$buf
Last edited by catkin; 12-04-2011 at 10:02 AM.
|
|
|
|
12-05-2012, 03:06 AM
|
#7
|
|
LQ 5k Club
Registered: Dec 2008
Location: Tamil Nadu, India
Distribution: Debian Squeeze (server), Slackware 13.37 (netbook), Slackware64 14.0 (desktop),
Posts: 8,358
|
The information in post 6 is outdated. Under Slackware64 14.0 (kernel 3.2.29, udev 182, sdparm 1.07) the SCSI pass-through device files are /dev/bsg/*, not /dev/sg* as earlier.
The general technique of using the SCSI pass-through device file as the sdparm DEVICE argument when sending commands is still valid.
Here's a function to implement it (logging and error trap code removed for clarity)
Code:
#--------------------------
# Name: spin_down_drive
# Purpose: spins down the drive
# Global variable used: $disk_dev_file
#--------------------------
function spin_down_drive {
local dev_bsg_X
# Get the /dev/bsg/* file name from the /dev/sd* file name
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# /dev/bsg/* is the "pass-through" interface to the SCSI driver. It must
# be used as the DEVICE argument when sdparm is used to send commands to
# the device.
dev_bsg_X=/dev/bsg/$( lsscsi --generic | grep $disk_dev_file | sed -e 's/\[//' -e 's/].*$//' )
# Spin down
# ~~~~~~~~~
sdparm --command=stop --quiet $dev_bsg_X
} # end of function spin_down_drive
EDIT: $disk_dev_file is, for example, /dev/sdc
Last edited by catkin; 12-05-2012 at 03:12 AM.
|
|
|
|
| Thread Tools |
Search this Thread |
|
|
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -5. The time now is 01:01 AM.
|
|
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.
|
Latest Threads
LQ News
|
|