Unmounting external USB drives in Ubuntu 9.04

UPDATE root issue discussed below was fixed in Ubuntu 9.10 – unmounting and spinning down the drive works just fine from GUI

As some people know there are problems with correct unplugging of external USB hard drives on linux, Ubuntu in particular. The problem is that none of graphical managers support drive spindown when unmounting/ejecting the external hard drive. This can cause severe damage to HDD.

There’s an old bug in LP for that. The summary is that you can use a script by Clem-Vangelis and Dave Rosky to do the work. Later someone posted a link to scipt by Yan Li, which does even some more stuff.

But since Ubuntu 9.04 those scripts have stopped working – the drive kept spinning. Googling around I found a similar problem and found out the problem was in udev rules. There was a manul solution in comments of LP bug #388506 mentioning udev rules editing: in ubuntu 9.04 I needed to copy the 60-persistent-storage.rules file, modify ACTION!="add|change" to ACTION!="add". This way the scripts worked. Somehow when this rule is present my home partition couldn’t be mounted. So this rules file must be removed after external drive unmount.

To make things easier I merged the scripts by Clem-Vangelis,  Dave Rosky and Yan Li, updated it with udev rules workaround and done some other things. The script is as follows:

#!/bin/bash
#
#  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
#
#  CHANGELOG:
#  v2 11 Aug 2009 by Sergei Sergejev https://sserzant.wordpress.com
#   * using predefined mountpoint instead of device drive name from parameters
#   * checking if sdparm is installed (bu default it's not)
#   * added unmount command from the script by Clem-Vangelis and D. Rosky
#   * created dynamic logic to process both mounted and unmounted drives
#   * some validation for the input
#   * updated for ubuntu 9.04 to overcome udev rule conflict
#   

# change this to match your drive's mountpoint
MOUNTPOINT='/media/wd'

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.

NB!
Before using this script set correct mountpoint.
In file manager (Nautilus) right click on the drive
and put 'Location' and 'Volume' together.
Volume is usually the name of the drive assigned during file system format

usage:
$0 [options]

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

if which sdparm >/dev/null; then
    echo ""
else
    echo "sdparm is not present. Install it by running sudo apt-get install sdparm"
fi

# root check
if [ `id -u` -ne 0 ]; then
    1>&2 echo error, must be run as root, exiting...
    exit 1
fi

# See if drive is mounted

CCOUNT=$(grep $MOUNTPOINT /etc/mtab | wc -c)
if [ $CCOUNT -eq 0 ]; then
  echo "No mountpoint $MOUNTPOINT. It is most likely you unmounted the drive"
  echo ""
  echo "Do you want to continue and try to suspend the device anyway? y/n"
  read ANSWER
  if [ $ANSWER = "Y" ] || [ $ANSWER = "y" ]; then
   echo "Plese give drive name eg /dev/sdb"
   read DEV_NAME
   if [ -b $DEV_NAME ]; then
     echo "Will try to suspend device $DEV_NAME"
   else
     echo "no such drive. Find out the drive name and run the script again"
     exit 1
   fi
  else
   echo "exiting"
   exit 1
  fi
else
  #Get the physical device from mtab
  DRIVELINE=$(grep $MOUNTPOINT /etc/mtab)
  LENG=$(expr match "$DRIVELINE" '[A-Za-z0-9\/]*')
  DEV_NAME=$(expr substr "$DRIVELINE" 1 $LENG) 

  #Try Unmounting the drive
  echo "unmounting $MOUNTPOINT"
  umount $MOUNTPOINT
  sleep 3
  CCOUNT=$(grep $MOUNTPOINT /etc/mtab | wc -c)
  if [ $CCOUNT -ne 0 ]
  then
   echo "Could not unmount $MOUNTPOINT, drive may be in use."
   echo "Verify all files and programs that may use the drive are closed"
   echo "and run the script again"
   exit 1
  fi
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

# hack for ubuntu 9.04 to overcome the udev rules
# please note that leaving this rule may leave your home folder unmountable
# if this happens remove the rule (sudo rm /etc/udev/rules.d/60-persistent-storage.rules) and reboot
RULE_BEFORE="ACTION!=\"add|change\", GOTO=\"persistent_storage_end\""
RULE_AFTER="ACTION!=\"add\", GOTO=\"persistent_storage_end\""
sed "s/$RULE_BEFORE/$RULE_AFTER/" /lib/udev/rules.d/60-persistent-storage.rules >> /etc/udev/rules.d/60-persistent-storage.rules

# flush all buffers
sync

# 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"
    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.

Read
http://elliotli.blogspot.com/2009/01/safely-remove-usb-hard-drive-in-linux.html
for an detailed explanation.
EOF
        exit 3
    fi

    [[ $VERBOSE == 1 ]] && echo "Suspending $DEVICE by writing to $POWER_LEVEL_FILE"
    echo 'suspend' > "$POWER_LEVEL_FILE"
fi

if [ -s /etc/udev/rules.d/60-persistent-storage.rules ]; then
  echo "removing temporary udev rule"
  rm /etc/udev/rules.d/60-persistent-storage.rules
else
  "temporary udev rule file not found... something went wrong"
fi

echo ""
echo "Drive can be safely unplugged"

To use the script

  1. copy it contents to a text file. Name it unmountwd (for example)
  2. put the correct mountpoint in the beginning of the script
  3. make it executable: right click on it in file manager, go to ‘Permissions‘ tab, tick ‘Allow executing‘ or run chmod +x in terminal
  4. move the file to /usr/bin
  5. enjoy it by calling from a terminal sudo unmountwd
  6. push Gnome, KDE, XFCE developers to develop proper external drive unmount in file managers

Update: some drives are still not spinned down correctly – there was one issue in Launchpad and one in the comments by Sap. In both cases the drive didn’t do so both on Windows and Mac too. This a hardware, not software issue.

You are welcome to comment and fix/post bugs (if any) 🙂

Advertisements

27 Comments

  1. ” 6. push Gnome, KDE, XFCE developers to develop proper external drive unmount in file managers”

    >>> I think it should be properly well integrated upstream…It is not a desktop environment stuff…

    Like

  2. Thanks a lot for this!

    I actually changed line 40 to read “MOUNTPOINT=$1” so that I could pass the mount point at the time of command since I use a couple drives with custom mount points

    now I run “sudo ~/bin/nameofscript /media/wd320”

    Like

  3. Thanks a lot for the script. I think it is working, though I am not sure. Should the device power down as well? The USB light is still on, but the drive does spin down. I’m running 9.04 as well, with a WD Passport. I don’t have a windows machine, so not sure what it is normally supposed to do. Thanks!

    Like

    1. I have the same drive and blue led is on after the script is executed. Originally drive and led should be powered off. I believe it’s enough if the drive did spin down 🙂

      Like

  4. Hello

    I’ve been trying all sort of techniques to spin down my external usb hard drive without any success. My trials included utilities like sg3-utils, scsiadd, hdparm, sdparm, sg_start, umount, eject and Yan Li’s and your scripts.

    All of them have failed to get my drive to spin down. The maximum I’ve got working with your script is that the usb-light turns off but the hard drive still keeps spinning, which results in the typical emergency-park ‘click’ sound.

    I’d appreciate any help in this regard. I’m running Xubuntu 9.04 on a Dell D600 laptop and my external hdd is a 100GB Fujitsu laptop one (sata) for which I’m using a generic external usb-enclosure.

    Thanks.

    Like

  5. My system has an external USB WD MyBook Essential (1Tb).
    When I first tried this script (very nice) to stop it I had the same problem of Sap: disk light was still on after script completion.
    I tried to ececute by hand all the command script do… I was surprised that in this case the disk was correctly stopped.
    After some more tests I found this behaviour on my USB disk: when the commands “sdparm –command=sync” and “sdparm –command=stop” are sent to the device, sdparam immediately return to unix prompt but the disk lignt flashes for some seconds (6-12″ for sync command and 5″ for stop).
    I inserted in the script a copule of sleep just after these two commands of 20 and 10 seconsds and now it switch off correctly all my USB devices.
    Sleep in procedures is not a nice solution. If someone have an idea of how to check when disk has completed the command this will make a perfect solution.

    Like

  6. Dear Sergei,

    I would like to thank you as well, but being a newbie (to Ubuntu (running 9,04), to Linux) this is rather challenging for me… please delete my post if you think I am polluting your page. I am hoping this may help others in the same boat, as this should be a common functionality for an operating system… in my opinion.

    I followed your instructions, was able to do #1…
    #2 also I think, I changed the line to ” MOUNTPOINT=’/media/My Passport’ ”
    #3 OK
    #4 was difficult, as I could not drag and drop the file in the location you are instructing. Instead, I found the command ” gksudo nautilus ”
    This opened another Terminal window, and I was able to move the file.

    I am stuck with #5; here is the return from Terminal:

    christoph@christoph-laptop:~$ sudo unmountwd
    [sudo] password for christoph:
    sdparm is not present. Install it by running sudo apt-get install sdparm
    grep: Passport: No such file or directory
    No mountpoint /media/My Passport. It is most likely you unmounted the drive

    Do you want to continue and try to suspend the device anyway? y/n
    y
    Plese give drive name eg /dev/sdb
    /dev/sdbb
    no such drive. Find out the drive name and run the script again

    How do you find out the drive name? I tried ” /dev/sdbb ” but obviously that didn’t work.
    I figured out that sdparm was missing and tried to install it:

    christoph@christoph-laptop:~$ sudo apt-get install sdparm
    Reading package lists… Done
    Building dependency tree
    Reading state information… Done
    The following packages will be REMOVED:
    dcp8020lpr
    The following NEW packages will be installed:
    sdparm
    0 upgraded, 1 newly installed, 1 to remove and 3 not upgraded.
    1 not fully installed or removed.
    Need to get 116kB of archives.
    After this operation, 381kB of additional disk space will be used.
    Do you want to continue [Y/n]? Y
    Get:1 http://us.archive.ubuntu.com jaunty/universe sdparm 1.02-1 [116kB]
    Fetched 116kB in 1s (97.1kB/s)
    (Reading database … 129079 files and directories currently installed.)
    Removing dcp8020lpr …
    /var/lib/dpkg/info/dcp8020lpr.postrm: 3: /etc/init.d/lpd: not found
    dpkg: error processing dcp8020lpr (–remove):
    subprocess post-removal script returned error exit status 127
    Errors were encountered while processing:
    dcp8020lpr
    E: Sub-process /usr/bin/dpkg returned an error code (1)

    …..
    Didn’t know what to do then.
    I tried System/Administration/Synaptic Package Manager, looked for sdparm, found it, but could not install it (a bunch of error messages that were Chinese to me).

    Probably, the fix is very simple…. but I’m still learning and this is too challenging for me.
    Anyone want to help??!
    Not sure this is needed, but I am running 9.04, Toshiba Tecra A10, and WD Passport 320GB.
    Thanks a lot, and I’ll post again (results?) if I get some help!
    Christoph

    PS: in the meantime, I just unplug the hard drive “hot”… not great.

    Like

    1. >as this should be a common functionality for an operating system
      it’s sure must be

      /dev/sdbb is incorrect drive name. It consists only of 3 letters as far as I know

      Please plugin your drive and tell me the name of the drive icon on the desktop that will appear

      then open a terminal and run this
      ls /dev | grep sd

      and then
      ls /dev | grep hd

      Please post the results here. Then I will know if you use SATA or ATA drive. Knowing this I’ll be able to give further instructions. Be sure to back-up most valuable information and don’t forget at least to unmount the drive in GUI from the desktop (right click the drive icon)

      To fix the package manager (apt) run
      sudo apt-get install -f

      Like

      1. Hello Sergei,
        Thank you for taking the time to help me out.

        Here’s the name of my drive, once plugged in: “My Passport”

        Here is the Terminal output after both commands:

        christoph@christoph-laptop:~$ ls /dev | grep sd
        sda
        sda1
        sda2
        sda5
        sdb
        sdb1
        christoph@christoph-laptop:~$ ls /dev | grep hd
        watchdog
        christoph@christoph-laptop:~$

        As for the package manager errors, I’ll try to fix it myself as I believe this is a separate issue and doesn’t belong here. “sudo apt-get install -f” still returns errors.

        Christoph

        Like

    2. @Christoph S

      Blind me 🙂 You use different quotes in MOUNTPOINT. You should use
      MOUNTPOINT=’/media/My Passport’

      Note that you used ´ instead of ‘, these are different things. If above doesn’t help, try to use MOUNTPOINT=’/media/My\ Passport’

      And FYI your drive name is /dev/sdb

      Like

  7. Sorry for the newbie question, but will this work on Ubuntu 9.10? I’m planning on buying a WD My Passport for Christmas, and I want to use this drive for my computer (that runs 9.10). I have made the script and uploaded it to http://dl.getdropbox.com/u/1928926/unmountwd

    Changes I made:
    1) added some text to the begining (comments)
    2) added some sleep commands to one part so it looks like this:
    # 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
    sleep 12
    # send SCSI stop command
    [[ $VERBOSE == 1 ]] && echo “Stopping device $DEV_NAME”
    sdparm –command=stop “$DEV_NAME” >/dev/null
    sleep 5

    Like

    1. There were messages in the bug mentioned in the post that unmounting and suspending the drive works from GUI in 9.10.
      I have a WD Passport and I’ll be able to answer if it works with it in a few days 🙂

      Like

      1. I saw the integrated “Safely Remove Hardware” with nautilus on 9.10. Really nice.

        Is it possible to have it on 9.04? Perhaps a nautilus script?

        Like

  8. I also found that GNOME 2.28 has the “eject” function now. But I can’t find detail introduction of it. Anyone has such kind of info? Thank you.

    Like

  9. I also needed to add sleeps after the sdparm commands to put my WD Passport to sleep (running Kubuntu 9.10). Thanks a lot for the script.

    Like

  10. I ran into the same issue while looking for these scripts to make my backup drive save power and found out what was really going on. The issue is indeed exactly the same as with the hdparm bug you referenced. Both the linux command line utils that can be used to send these commands to the drive erraneously open the disk in read-write mode and it’s fixed simply by changing one line in for example sg_start. https://bugs.launchpad.net/ubuntu/+source/sdparm/+bug/444818 has more.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s