Tuesday, September 30, 2014

Every time you lie... Git kills a kitten

Just wasted over two hours of my life over the fact that Git no longer supports $GIT_CONFIG.  Except when it FUCKING PRETENDS TO.

(I'll skip filing a bug report for the moment -- otherwise it would probably only be a stream of swear words.)

(Followup:  I filed Debian bug#763712, and was amazed at how quickly the dev team reacted.  Kudos!)

Monday, September 29, 2014

An interface by any other name

If you're looking to rename a network interface, you've probably come across this udev rule:
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:00:00:00:00:00", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="foo"
And if you're like me, you won't be content to blindly copy this rule without understanding what it all means.  What are all those constants for?  Why can't we go for the simpler example shown in Writing udev rules:
KERNEL=="eth*", ATTR{address}=="00:00:00:00:00:00", NAME="foo"
What's wrong with that?

Well, I don't know if it could be said that there's anything wrong with that rule (other than the fact that it is uselessly invoked for any other action), but it does lack precision; if there ever is, say, an "ethical" device that happens to have the same address attribute (maybe it is attached to the Ethernet port and inherited the attribute), it would also match that rule.  Probably unlikely, but you never know.

Here's the first rule again, deconstructed:
  • ACTION=="add"
    This shouldn't need any explanation. :)
  • DRIVERS=="?*"
    Ensures that there is a driver for this device (i.e. it is a physical device, not a virtual one).
  • SUBSYSTEM=="net"
    Provides a context for the following attributes (since there could be other subsystems with the same attribute names).
  • ATTR{type}=="1"
    Ethernet hardware type (defined in if_arp.h)
  • ATTR{address}=="00:00:00:00:00:00"
    MAC address, obviously :)
  • ATTR{dev_id}=="0x0"
    In case there are multiple devices with the same MAC address, this matches the first one.
(You can get more information about the various attributes for a net device.)

I think that with all of the above, the KERNEL match is superfluous; although I guess it could be used to skip any interface that has already been renamed to something other than "eth*".

And now I can rest, contented.  :)

Not all USB flash drives are removable

I had assumed that all USB flash drives had a removable sysfs attribute of 1 (they are, after all, "removable"), but this is not always true.  (Apparently, removable was meant for floppy drives and such, where the media is removable.)

Curiosity got the best of me, and I tried to figure out where that value was coming from.  Google suggests that this is a USB property, but lsusb shows nothing to that effect.  Searching the kernel code didn't help much; the end result comes from the GENHD_FL_REMOVABLE gendisk flag, set by sd.c from the struct scsi_device removable flag, set by, erm, someone.

To make things more confusing, there is another removable sysfs attribute for USB devices, supposedly "inferred from a combination of hub descriptor bits and platform-specific data such as ACPI", whose description sounds like what I wanted in the first place, but which always returns unknown for me.

That was an hour well wasted.

Sunday, September 28, 2014

$? is a very fragile thing

#!/bin/sh

foo() { return 42; }

while ! foo; do
    if [ $? -eq 42 ]; then
        echo "foo() has indeed returned 42"
        # This should exit with a status of 42, right?
        exit $?
    fi
done
It should've dawned on me that there's a glaring bug in there: the return status of the echo command will overwrite the value of $?.

It took me some more time to realize that there are two bugs in there: ! is also a command, and its return status will also overwrite $?.

It took me much longer to realize that there are three bugs in there: [ is also a command, and yadda yadda yadda.

God I get tired of this shit sometimes...

Saturday, September 27, 2014

The Five Ws (and a few more letters)

More information than I'd ever thought possible about the multiple ways of knowing if/where a command is available from a shell.  My head is still spinning.

As thick as a phonebook

$ cat .
cat: .: Is a directory

$ perl -pe '' .
Huh.

Turns out you can open(2) a directory just fine, so Perl doesn't bat an eye over that.  It's only the subsequent read(2) that will fail, indicated by readline (or read, or sysread) returning undef, which you're supposed to check for.  Yeah, like anyone actually checks for these things.

The worst part is that autodie won't save your ass in this situation:
use autodie;
use warnings;

open FH, "<", ".";
1 while <FH>;
close FH;

print "Uncaught: $!\n";
Apparently, not much can be done about this.  :(

Letter to cgmanager: You have to learn to let go

This is the second time that I'm unable to unmount an otherwise non-busy block device, only to find out (after killing nearly every process, forcing me to reboot anyway) that the culprit is cgmanager.  This is quite annoying.

(Nobody else on the interwebs seems to have this problem.  Oh well, at least I'll know who I should yell at next time.)

Unclogged

Older, wiser programmers will have figured out that the bug in my previous post was due to an uncaught SIGPIPE.  I'd completely forgotten about those.  :)

The shifting nature of the bug (which is what really had me confused) was due to a race condition between the parent process writing to the pipe, and the child closing it (by exiting).  Here's an overly simplified version of what happens in both processes after the clone():
/* parent - writer */
write(...)
close(...)
waitpid(...)

/* child - reader */
execve("/bin/false", ...)
exit(1)
If the parent attempts to write to the pipe after it has been closed on the other side by the child's exit(), then a SIGPIPE obviously occurs.  However, since our simple string is small enough to fit in a pipe's buffer, the parent may very well get the chance to close the pipe before the child.  At this point, the child's close() will simply discard any data in the pipe's buffer and exit; its exit status will then be returned by the parent's waitpid(), stored in $?, and cause Perl's close() to return a false value.

The script example given in the previous post is simple enough (without autodie) for the parent to get there first.  Adding autodie then introduces just enough complexity for the parent to take a little bit more time, giving the child a chance to exit first.  (Even using strace is enough to influence the result, making this a true heisenbug.)

Note that in the parent-closes-first case, the failure has nothing to do with the pipe itself (hence why $! is left empty), but is simply due to the child returning a non-zero exit status.  (Thus, replacing false with true would make the script fail some times, but not always.  Now there's a real head-scratcher.)

Thursday, September 25, 2014

Clogged

This one had me stumped for a while:
#!/usr/bin/perl

use 5.014;
use autodie;

open FALSE, "|-", "false";
print FALSE "Hello world!";
say "Closing filehandle:";
close FALSE or die "close() doesn't die...";
say "...but doesn't succeed either";
What made it even confusing was that commenting out autodie allowed close to at least (silently) fail and return a false value (without even an error message in $! as would be expected).  I used autodie to catch any unchecked errors, dammit, not to hide them even further!

I guess that's what I get for living a life sheltered away from the raw, bare-metal, non-child-proof world of C programming.  :)

I Can Haz Triplequotes?

Writing a udev rule:
[...] PROGRAM="/bin/sh -c '... something-that-must-be-quoted ...'"
Drat.  :(

Wednesday, September 24, 2014

Google+ and X-Sender-ID

I just realized today that although Google+ emails come from a non-descript "noreply" address, they include an X-Sender-ID header with the info I needed.  With that and a procmail recipe, I'm all set!  (I've had so very few moments recently where things just work, so that was appreciated.)

Tuesday, September 23, 2014

Snatching the root device from grub-install

# grub-install --debug --boot-directory /media/usb/boot /dev/sdh
[...]
grub-install: info: guessed root_dev `hostdisk//dev/sdh' from dir `/media/usb0/boot/grub/i386-pc'.
grub-install: info: setting the root device to `hostdisk//dev/sdh,msdos1'.
[...]
Goddamit, GRUB!  This is not what you've just set as root device -- core.img cannot possibly know what /dev/sdh means.

I can't believe I have to resort to strace to get the right answer:
write(2, "grub-mkimage --directory '/usr/lib/grub/i386-pc' --prefix '(,msdos1)/boot/grub' --output '/media/usb0/boot/grub/i386-pc/core.img' --format 'i386-pc' --compression 'auto'  'ext2' 'part_msdos' 'biosdisk' \n", 203) = 203
There: "(,msdos1)" is the actual root device.  Was that too much to ask?

A nicer alternative to init=/bin/sh

My thanks to the Gentoo guys for showing succintly how to solve the whole "/bin/sh: can't access tty; job control turned off" problem when booting directly into a shell.  With that and a call to /etc/init.d/rc, I got myself a simple script to automatically login as root into runlevel S and stay there permanently:
/etc/init.d/rc S

while true; do
    setsid sh -c 'exec login -f root </dev/tty1 >/dev/tty1 2>&1'
done