Sunday, January 10, 2010

Automate gadget-related tasks by setting up a udev rule

If you're reading this post, odds are good that you own a computer.  Since it is specifically this post that you're reading, odds are good that you also have some kind of peripheral, such as a camera, printer, mp3 player, webcam, usb flash drive, usb hard drive, etc, and that there are certain tasks that you need to perform every time you plug it in.

For instance, you may want your computer to automatically import your pictures when you plug in your camera.  You may need to send your printer its firmware (like I do) every time you plug it into your computer.  You may want to back up your phone or send files to your flash drive automatically.  Or perhaps you just want Skype to launch automatically whenever you have your webcam connected.  Whatever your situation, odds are good that there is some way that you can automate tasks and make your life easier by saving time with udev rules.

Udev is the new device manager for the Linux kernel, replacing the older manager, hotplug.  In this post I will show you how to configure a rule so that udev runs a bash script (which I will also show you how to create) whenever you plug in your device.

Creating the udev rule

The nice thing about udev is that you can make rules for individual devices.  This means that you can have a different procedure run when you plug in your external hard drive than the one that runs when you plug in your USB flash drive.  To get started, plug in the device for which you would like the rule created.  Then run lsusb.

You will get a list of 5 or 6 items, and you should see your device listed by manufacturer.  On that same line, just before the manufacturer's name, you should see two 4-digit alphanumeric values seperated by a colon.  The first value is the Vendor ID and the second is the Product ID; you will need both for your udev rule.  As an example, when I created my rule for my mp3 player, this was the output of lsusb:
Bus 001 Device 012: ID 041e:4139 Creative Technology, Ltd Zen Nano Plus
Bus 001 Device 002: ID 0c45:62c0 Microdia Sonix USB 2.0 Camera
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

You can see my mp3 player listed on the first line, and that the Vendor ID is 041e and the Product ID is 4139.  On to the next step.

Go into /etc/udev/rules.d and, as root, create a new file named 85-my_rule.rules (the file must begin with a 2-digit number followed by a hyphen, and it must end with ".rules"; it doesn't really matter what you put in the middle) and open it in your favorite text editor.  Enter the following:

     ACTION=="add", ATTRS{idVendor}=="your-devices-vendor-id", ATTRS{idProduct}=="your-devices-product-id", RUN+="/path/to/your/bash-script"


...so the rule I created for my Zen Nano looked like this:

     ACTION=="add", ATTRS{idVendor}=="041e", ATTRS{idProduct}=="4139", RUN+="/home/jizldrangs/bin/sync-zennano"


This basically tells udev that whenever you plug in a device with the vendor ID and product ID that you specified, it is to execute the bash script that you specified.  With this setup, the options for what you do when the device is plugged in is only limited by what can be scripted in Bash, so you can see how powerful this can be.

If for whatever reason you would like to manually mount or unmount the volume, you can tell udev to place a symlink in the /dev directory by using the SYMLINK keyword.  Just append SYMLINK+="my_symlink_name" to the end of the rule, and after it runs you can find a symlink to the device in /dev which you can use with the mount or umount commands.

When you are finished with that, restart udev:

     sudo service udev restart

Creating the script

The script that is run when the device is connected can do pretty much anything you want.  As long as it can be done from the command line, you can automate it with udev. 

As an example, and as a segue into my discussion on the various pitfalls of udev, here is what I ended up with:

#! /bin/sh

USER=jizldrangs
export HOME=/home/jizldrangs
SHELL=/bin/sh
DISPLAY=:0.0
MAILTO=jizldrangs
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/home/jizldrangs/bin
xhost local:jizldrangs
export DISPLAY=:0.0
LOGFILE="/usr/local/bin/sync-zennano.log"
TIMEFILE="/usr/local/bin/sync-zennano.time"

CURRENTLY=$( date +%s )
if [ -e $TIMEFILE ]; then
  
    echo "sync-zennano.time exists"
    FIRST_RUN_TIME=$( cat $TIMEFILE )
  
    echo $CURRENTLY
    echo $FIRST_RUN_TIME
    NUMOFSECONDS=$(($CURRENTLY - $FIRST_RUN_TIME))
    echo "num of seconds: ${NUMOFSECONDS}"
    if [ $NUMOFSECONDS -gt 60 ]; then
        echo "sync-zennano has not run in over 60 seconds"
        rm -f $TIMEFILE
    fi

fi

if ! [ -e $TIMEFILE ]; then

# Send the following block of commands to the background
{
    sleep 10

    notify-send -u normal -t 2000 -i info "Zen Nano Sync" "Starting synchronization.  Please wait."

    sudo -u jizldrangs unison zennano -auto -batch -terse -ui text

    notify-send -u normal -t 2500 -i info "Zen Nano Sync" "Synchronization of Zen Nano is complete"
} &
    touch $TIMEFILE
    chmod 666 $TIMEFILE
    date +s% > $TIMEFILE

fi

There are several udev "gotchas" that I'm working around here (I only really wanted to run 3 lines, but had to add a bunch of other stuff to get it working right).  It's worth going through each one...

Udev Gotchas

There are a relatively large number of things to watch out for:
  1.  For some reason, it is common for udev to run your script many times in quick succession.  Whenever I plug in my mp3 player, udev runs my script at least 10 times.  To get around that, I added a few lines to my script that would write the time to disk the first time it is run, then check for the existence of the file and not run the payload of the script if it had already been run within the last 60 seconds.  It's clunky but it works.
  2. Make sure you make the script executable with a "chmod 700 /path/to/script".  
  3. Keep in mind that your script will be run as root.  This means:
    1. For security reasons, keep it in a folder where it can't be accessed by anyone but root and remove read/write permissions from all other users, including yourself.
    2. Any environment variables or elements you may be relying on won't be available.  In my script, I am running a Unison profile located in my home directory, so in order to get it working I had to export the path to my home directory into the HOME variable in the first few lines of the script.  Then when it came time to run Unison, i added "sudo -u jizdrangs" at the beginning of the line, which causes that line to be run as me rather than as root.
  4. Udev will wait for your script to finish before mounting the device, so if you are doing any tasks that rely on the device being mounted, send that series of commands to the background by surrounding them with curly braces and adding a space and an ampersand after the close-curly-brace.  This will "detach" the commands, or cause udev to continue its work without waiting for those commands to finish.  Then add a "sleep 10" at the beginning of the commands being sent to the background, so that udev has time to mount the device before the rest of the commands are executed.
  5. Because udev runs the script in its own environment, it won't have information on your display.  I like to have notifications pop up, so I added the "export DISPLAY=:0.0" line towards the top.  The notifications I get using notify-send would not work without it.
It took me a while to get all this working, but it was worth the knowledge I gained along the way.  Now whenever I plug in my mp3 player, it automatically synchronizes my podcasts, and it displays notifications telling me when it begins the sync process and when it is finished.

See also:
http://reactivated.net/writing_udev_rules.html
http://ubuntuforums.org/archive/index.php/t-502864.html

Unify your files with Unison

If you are using multiple computers, you must have some way of keeping the files you want on each machine.  A lot of people use Dropbox or LiveMesh, and Canonical recently threw its hat into the ring with UbuntuOne, but all of these solutions provide a pretty small amount of storage (2 gigs in the case of Dropbox and UbuntuOne), and as with any online storage solution they also require that you trust them not to lose your data and to respect your privacy.

Because I prefer not to trust anyone with my data unless I have to, and partly because I'm a cheapskate, I prefer the DIY approach.  This is where Unison comes in.

Unison Explained

Unison is a file synchronization utility that keeps the contents of two directories synchronized.  In true Unix fasion, it doesn't reinvent the synchronization wheel, instead it uses rsync to do the comparison and synchronization, effectively making a tool that could only handle one-way synchronizations capable of handling two-way synchronizations.  In addition to being able to sync local directories, it can also operate over SSH, so you don't have to spend a bunch of time worrying about providing unison a protocol or dedicated port, or setting up a dedicated authentication mechanism.

Like all great Linux utilities, Unison has a graphic user interface but can also be used on the command line (this is important for automating synchronizations in cron; more on that later).  Either way, you are going to manage Unison tasks with Profiles.  Profiles are basically a set of two folders that you are syncing (one local, the other local or remote), and zero or more options, and although they can get rather elaborate, don't let it scare you.  The Unison GUI makes the task of creating a new profile simple.

Creating Unison Profiles
  
After you've installed Unison (on Ubuntu use "sudo apt-get install unison unison-gtk"), fire it up from your menu or by typing "unison-gtk" into the command line.  You will be greeted with a screen where you can type in the name of your first "root", or local directory.  When you are finished with that, hit OK and then you will be promted to enter the second directory.  This screen will give you the option to connect to a remote machine over SSH or a raw socket (not recommended).  If you decide to sync with another machine over SSH, be sure to type in the absolute path to the folder on that machine (i.e. "/home/jizldrangs/documents").  Fill in the host name, username to connect as, and port name as necessary, then hit OK and you're done!  Unison will take this information and create a new profile called "default".  You can create new profiles the next time you launch the Unison GUI

The GUI will help you set up a profile and will allow you to set some of the basic options, but if you want to use any of the more advanced features (see the Unison man page for a list), you will need to edit the profile by hand.  Your profiles are stored in separate files in the .unison directory in your home directory (e.g. /home/jizldrangs/.unison; it is a hidden directory).  In that folder you will see a file for each of your profiles, all ending with ".prf".  Simply open the profile you want and start adding options.  You can do things like exclude certain file types, exclude certain directories, exclude files over a certain size, have it follow links, etc. 

I already have an old Pentium 3 laptop recommissioned as our file and print server, and it runs all the time, so it was the perfect place to store the master copies of all my files, and to receive updates from whichever of my computers I make changes on and distribute them to the rest of the machines, all within the security and ease of SSH.

To make sure that I have the latest version of my files, I've tasked Cron, my personal Linux butler, with the task of running the sync at the bottom of every hour.  Here is the relevant line from my crontab file:

# m h  dom mon dow   command
30 * * * * unison default -ui text -batch

The "ui text" option tells unison to use command-line mode and not to launch the gui, and the "batch" option tells it to accept default update options (basically, replace older files with newer files), so it doesn't prompt me for input on what to do with the files.  Now my file syncing is totally automated.

Unison Gotchas

If you are syncing files to an mp3 player or usb flash drive, add the option "perms = 0" to avoid getting the error "failed to set permissions" when you try to sync.

Happy syncing!  

Saturday, January 2, 2010

Ubuntu Netbook Remix

I received an Acer Aspire One netbook for Christmas, and after installing Ubuntu Netbook Remix it has replaced the Dell Inspiron 5100 laptop as my main day-to-day machine.   UNR is a very slick modification of Ubuntu that is well-suited to this type of computer.  Some initial thoughts:
  1. Hulu works!  Yay!
  2. The smaller screen has been harder to get used to than I thought it would be.  I always thought of the larger screen size of my Dell Inspiron laptop as an asset, I didn't appreciate what an asset it was until I switched to this 10.1" screen.  Fortunately UNR has a neat little feature that helps you get the most of your pixels: when a window is maximized, it will merge the title bar with the menu at the top of the screen.
  3. The battery life on this thing is fantastic.  Thanks to the power-sipping Atom processor, the battery will last about 5 1/2 hours.  The Dell laptop's Pentium 4 would guzzle down the battery's juice in about 45 minutes (if I was lucky).
  4. Unfortunately the touchpad driver does not support multitouch, so 2-finger scrolling does not work.  UNR provides an "edge scrolling" option, which is what I'm using now, but it would be great to get 2-finger scrolling back.
  5. Like other netbook OSs that I've seen, the menu is integrated with the desktop.  It looks really slick, is easy to use, and does a good job of utilizing the netbook's limited screen real estate.
I love this little unit, but I'm not sure that I agree with the initial netbook vision.  Netbooks were supposed to be little more than a dedicated web-browser, with most of the applications, and therefore computing, done in the cloud.   Although most of what I do on this machine involves the web browser, there are plenty of client applications I use.  Sure, I am not going to be running VirtualBox on this machine any time soon, but this machine wouldn't have one-tenth the value to me if it couldn't run applications like Liferea, Zim, Unison, Empathy, and Rhythmbox in addition to Firefox. 

Furthermore, I'm not sure that the manufacturer of this device really believes in the original vision for the netbook either.  The original netbooks (and here I'm speaking of the Asus Eee) had a single-core Celeron processor, with a trimmed-down customized version of Xandros Linux, and 4 gigs of internal flash storage.  That was much more consistent with the web-browser-only ideal than today's netbooks.  This Acer Aspire One has a dual-core Intel Atom 1.6 Ghz processor, 1 gig of RAM, and 160 gig hard drive, and it shipped with Windows XP Home Edition.  It seems clear to me that the public liked netbooks but wanted a higher level of functionality than was available in the first generation of netbooks, and Acer along with the manufacturer of every other netbook I am aware of, has delivered.  I'm absolutely thrilled with the results.

It appears that some hardware manufacturers have picked up on the recent netbook trend and have decided to declare war on it.  Litl, LLC along with a few other companies are attempting to return to the original netbook vision with the "webbook", which is truly a web browser with a keyboard attached.  I wish litl the best of luck in their endevours, but I fear for their sake that the days of terminal-mainframe topologies are behind us, and that people will always want their machines to have some modicum of capability.