Howto: Automatically spin down external usb hard drives in Ubuntu


Spinning down external usb hard drives in Ubuntu doesn’t allways happen automatically in Ubuntu. To force a spindown, you can issue the command

sync
sdparm --flexible --command=stop /dev/sdX &>/dev/null

where you should replace the sdX entry by the correct name of your external hard drive (usually sdb, sdc or similar).
To do this automatically every 30 minutes, save the code below as e.g. ~/bin/spindown/spindown.sh.

# !/bin/sh

# Get new state from diskstats
NEWstate=$(cat /proc/diskstats | grep $1)
echo $NEWstate > NEWstate.txt

# compare md5 sums
md5new=$(md5sum NEWstate.txt | sed 's/ .*//')
md5old=$(md5sum OLDstate.txt | sed 's/ .*//')

# if no changes, power down
if [ "$md5new" = "$md5old" ]; then
	sdparm --flexible --command=stop /dev/$1 &>/dev/null
fi

# Write current state to file
echo $NEWstate > OLDstate.txt

Then, add the entry

*/30 *   * * *   root	sh /home/user/bin/spindown/spindown.sh sdX

to the cron file /etc/crontab. The script automatically checks to see if the drive has been active for the last 5 mins. If not, it forces a spindown.

Be Sociable, Share!

23 thoughts on “Howto: Automatically spin down external usb hard drives in Ubuntu

  1. Andy

    Great post – works really well. Thanks.

    If anybody’s interested, I also added some code at the top of the script to check that it is being run as root…

    if [ "$(id -u)" != "0" ]; then
    echo “This script must be run as root” 1>&2
    exit 1
    fi

    Obviously this is not needed for cron, but useful for testing etc.

    Reply
  2. patrick

    hey!

    many thanks for the script.

    for external usb drive compatibility i added the following lines.

    #convert UUID to ID
    disk=$(blkid | grep $1 | cut -c 6-9)

    #check if the disk uuid, or pieces of it were ok
    if [ ! -n $disk ]; then
    echo “no such disk – disk!”
    exit 1
    fi

    cheers

    Reply
  3. Phil

    Are md5sum and sed really necessary? Could you not just do a diff between OLDstate.txt and NEWstate.txt?

    [ -z $(diff OLDstate.txt NEWstate.txt) ] && sdparm –flexible –command=stop /dev/$1

    Translation: Spin down when diff returns a zero-length string on comparing OLDstate.txt and NEWstate.txt. This gets rid of a few lines of code and reduces external dependencies.

    Reply
  4. Jos

    Awesome, just what I was looking for since my internal SATA HD’s would not spin down anymore after upgrade to ubuntu 10.04

    Reply
  5. whackedout

    */5 * * * * root sh /home/user/bin/spindown/spindown.sh sdX

    should the sdX be sda, sdb, sdc etc or should it stay sdX

    thanks

    Reply
  6. Rena

    # !/bin/sh

    if [[ -e /dev/$1 ]]; then
    echo $1 Spin power down
    notify-send –icon=’/usr/share/icons/GartoonRedux/scalable/apps/gaim.svg’ “Spinning Powerdown: $1″
    else
    # Get new state from diskstats
    NEWstate=$(cat /proc/diskstats | grep $1)
    echo $NEWstate > /var/ds.state/NEWstate.$1

    # compare md5 sums
    md5new=$(md5sum /var/ds.state/NEWstate.$1 | sed ‘s/ .*//’)
    md5old=$(md5sum /var/ds.state/OLDstate.$1 | sed ‘s/ .*//’)

    # if no changes, power down
    if [ "$md5new" = "$md5old" ]; then
    sdparm –flexible –command=stop /dev/$1 &>/dev/null
    fi

    # Write current state to file
    echo $NEWstate > /var/ds.state/OLDstate.$1

    echo $1 Device Does Not Exist !
    exit 0
    fi

    So… i put a notify in the script and the oldstat,txt is replaced with oldstate.$ so will permit to use it on more than one disk as the extension will be .sdX and made a sort of check if the disk exist, was a good idea and a good job to start wirh, thanks

    Reply
  7. Rena

    # !/bin/sh

    if [[ -e /dev/$1 ]]; then
    echo $1 Spin power down
    notify-send –icon=’/usr/share/icons/GartoonRedux/scalable/apps/gaim.svg’ “Spinning Powerdown: $1″
    # Get new state from diskstats
    NEWstate=$(cat /proc/diskstats | grep $1)
    echo $NEWstate > /var/ds.state/NEWstate.$1

    # compare md5 sums
    md5new=$(md5sum /var/ds.state/NEWstate.$1 | sed ‘s/ .*//’)
    md5old=$(md5sum /var/ds.state/OLDstate.$1 | sed ‘s/ .*//’)

    # if no changes, power down
    if [ "$md5new" = "$md5old" ]; then
    sdparm –flexible –command=stop /dev/$1 &>/dev/null
    fi

    # Write current state to file
    echo $NEWstate > /var/ds.state/OLDstate.$1
    else
    echo $1 Device Does Not Exist !
    exit 0
    fi

    ops i posted the wrong one… this one works

    Reply
  8. admin Post author

    @thinmintaddict

    It sounds as if a process is accessing the drive while your are trying to spin it down.
    You can use ”fuser -m /path/to/device” to get a list of processes accessing the device,
    and ”fuser -km /path/to/device” to kill the processes. Check out fuser’s manpage for more options.

    Reply
  9. Rena

    yes sdx could be sda sdb sde or so on, seems working fine here,but i found something similar:
    #!/bin/bash
    let a=0

    for i in `seq 0 100`
    do
    let a=`cat /proc/diskstats | grep “0 $1″ | awk ‘{print $(NF-2)}’`+a
    sleep 0.1s
    done
    echo $a
    if [ $a == 0 ]
    then
    sdparm -C stop /dev/$1
    fi
    exit 0

    seems working also, and no need to write down the stats….

    Reply
  10. Rena

    Even if i can say that the disk is almost powerd on if you even access to the desktop, i dont see much usefull this can be, the drive goes on off too many times and this is harmfull for it.

    i just finished my netgear script today, nice stuff, keepd up the line at the max speed connection than can get on that moment and changes speed if the s/n or attenuation changes on the line to not break down,it’s a sort of stay on the max speed the line can support on that moment.
    running that for a few days now, and i believe who is far from the box it’s a need.

    Reply
  11. Cheez

    @admin
    This script is nice ’cause it makes sure that there is no activity within a 10 second period. Run it periodically as a cron job.

    Reply
  12. amie

    Instead of /dev/sdX I’m using /dev/disk/by-uuid/$1 and address the drive by the uuid. That way it’s very easy to address a particular drive, as these a symlinks to the propper /dev/sdX. Udev does normally not guarantee that a certain drive is always mapped to the same /dev/sdX file but the uuid symlink does.

    Cheers

    Amie

    Reply
  13. dirk

    I added some checks and logging to the script…
    Now the spindown isn’t issued as long as the drive hasn’t spin up again.

    # !/bin/sh

    TMPF1=”/tmp/.hddspindwn-$1-1″
    TMPF2=”/tmp/.hddspindwn-$1-2″
    FLAG=”/tmp/.hddspindwnflg-$1″
    LOG_FILE=/var/log/hddspindown.log
    STAMP=`date +”%d.%m.%g %H:%M:%S”`

    [ ".$1" != "." ] && disks=$(ls /dev/sd* | grep $1)
    if [ ".$disks" = "." ]; then
    echo “Please enter a valid disk device” 1>&2
    exit 1;
    fi

    if [ "$(id -u)" != "0" ]; then
    echo “This script must be run as root” 1>&2
    exit 1
    fi

    # Get new state from diskstats
    nstats=$(cat /proc/diskstats | grep $1)
    echo $nstats > $TMPF1

    # compare md5 sums
    nmd5=”???”
    omd5=”???”
    [ -f $TMPF1 ] && nmd5=$(md5sum $TMPF1 | sed ‘s/ .*//’)
    [ -f $TMPF2 ] && omd5=$(md5sum $TMPF2 | sed ‘s/ .*//’)
    echo “$STAMP : Current MD5 : $nmd5″ >> $LOG_FILE
    echo “$STAMP : Old MD5 : $omd5″ >> $LOG_FILE

    # if no changes, power down
    if [ "$nmd5" = "$omd5" ]; then
    if [ ! -f $FLAG ]; then
    touch $FLAG
    hdparm -Y /dev/$1 &>/dev/null
    echo “$STAMP : Spindown of /dev/$1 intiated…” >> $LOG_FILE
    else
    echo “$STAMP : /dev/$1 was already set to standby.” >> $LOG_FILE
    fi
    else
    if [ -f $FLAG ]; then
    rm $FLAG
    echo “$STAMP : $FLAG removed because of disk-io.” >> $LOG_FILE
    fi
    fi

    # Write current state to file
    echo $nstats > $TMPF2

    Reply
  14. Egbert-Jan

    For a pata disk in an usb-enclosure the sdparm command seems not to work, nor does hdparm. Any suggestions highly appreciated!

    # sdparm -f -C stop /dev/mapper/udisks-luks-uuid-6872fba4-c251-4efc-b3ff-0d265fdh6c42-uid1000
    unable to access /dev/mapper/udisks-luks-uuid-6872fba4-c251-4efc-b3ff-0d265fdh6c42-uid1000, ATA disk?

    # hdparm -Y /dev/udisks-luks-uuid-6872fba4-c251-4efc-b3ff-0d265fdh6c42-uid1000
    issuing sleep command
    HDIO_DRIVE_CMD(sleep) failed: Inappropriate ioctl for device

    The disk is a pata 500GB WD drive.

    Reply
  15. Denis B.

    You help me a lot, because hdparm won’t work on my harddisk.

    I enhanced your script, to make log files.
    I use this on my server, made with desktop harddisks, that I don’t want to spin all the time.

    This script is written in bash instead of sh.
    The Cron intervall is the refresh rate of the logs

    #!/bin/bash
    #original from http://hartvig.de
    #modified By Denis B
    #soxnirvana@gmail.com
    #
    #
    #
    #
    #must be run as root
    if [ "$(id -u)" != "0" ]; then
    echo “This script must be run as root” 1>&2
    exit 1
    fi

    #————————
    # disc must be specified
    if [ ! $2 ]; then
    echo -e “\ntwo parameters needed:\n\n1)a disk must be specified : eg: sdb1, sdc3\n2)spindown time (since last access to disk)(Minutes)\n——————\nlog refresh time will depend to this script execution frequency\nspindown is \”2) + script_frequency\” maximum\n”
    exit 1
    fi

    #————————
    TMPDir=/tmp/hddspindwn
    TMPFold=$TMPDir/$1-1
    TMPFnew=$TMPDir/$1-2
    FLAG_OFF=$TMPDir/$1-hddspindwnflg-OFF
    FLAG_ON=$TMPDir/$1-hddspindwnflg-ON
    LOG_FILE=/var/log/hddspindown-$1.log

    #———————-
    #time
    day=$(date +%Y/%m/%d)
    hour=$(date +%X)
    seconds_alltime=$(date +%s)

    #———————-
    # Get new state from diskstats
    NEWstate=$(cat /proc/diskstats | grep $1)

    #create dir if doesnt exists
    if [ ! -e $TMPFnew ];then mkdir $TMPDir/ -p; touch $LOG_FILE;touch $TMPFnew;fi
    #get newstate file contents
    echo $NEWstate > $TMPFnew

    #exit if oldstate doesnt exists, but creates it
    if [ ! -e $TMPFold ];then
    touch $TMPFold;echo $NEWstate > $TMPFold;echo -e “\nfirst execution:\nlaunch again to compare\n”;exit 1;fi

    # compare md5 sums
    md5new=$(md5sum $TMPFnew | sed ‘s/ .*//’)
    md5old=$(md5sum $TMPFold | sed ‘s/ .*//’)

    #——————–
    # if no changes
    if [ "$md5new" = "$md5old" ]; then
    let “last_modified = ( $seconds_alltime – $(stat -c %Y $TMPFold)) / 60 ”

    #———————
    #enter spindown, if its the first time the device needs a power off
    if [ "$last_modified" -ge "$2" ] && [ ! -f $FLAG_OFF ] ;then
    touch $FLAG_OFF
    if [ -f $FLAG_ON ]; then rm $FLAG_ON; fi
    echo “$day;$hour;Power OFF” >> $LOG_FILE
    echo “POWER OFF”
    sdparm –flexible –command=stop /dev/$1 &>/dev/null
    fi
    echo -e “last activity detected : $last_modified (Min ago)”
    else
    #———————
    #activity on disk

    if [ -f $FLAG_OFF ]; then rm $FLAG_OFF;fi
    #if its the first time, then log power ON
    if [ ! -f $FLAG_ON ]; then touch $FLAG_ON;echo “$day;$hour;Power ON” >> $LOG_FILE;echo “POWER ON”;fi
    #writing the current state in a new file
    echo $NEWstate > $TMPFold
    fi

    #———————-

    “”

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>