New Laptop (aka Gentoo On MacBook Pro 5,5)

NB: I've cleaned up this guide and moved it, and I'll continue to update it there. This blog post will not get any more updates.

Those of you who have chatted with me over the past... er... 18 months or so... know I've been wanting a new laptop for a while. Those of you who have chatted with me over the past 6 months know that I'd settled on a 13" MacBook, but I wanted to wait until Apple's next refresh/update before making the purchase.

Apple updated their laptop line at WWDC in June 2009, and I was pretty much perfectly sold on the 13" MacBook Pro (one of the "updates" was to rebrand the 13" aluminum unibody MacBooks as Pro models).

I was in Maryland for a wedding, and decided to take advantage of the lower tax rate (than California's) and my sister's educational discount (she's a teacher). As a nice bonus of the edu pricing, I also got a free 8GB iPod Touch (which I really like a lot more than I expected to).

So ok, enough background.

Vital stuff: this describes my experience installing Linux on a June 2009 era MacBook Pro 13". DMI reports that this model name is "MacBookPro5,5". This guide may work for other similar systems, or it may not.

lspci output for this laptop:

00:00.0 Host bridge: nVidia Corporation Device 0a82 (rev b1)
00:00.1 RAM memory: nVidia Corporation Device 0a88 (rev b1)
00:03.0 ISA bridge: nVidia Corporation Device 0aae (rev b3)
00:03.1 RAM memory: nVidia Corporation Device 0aa4 (rev b1)
00:03.2 SMBus: nVidia Corporation Device 0aa2 (rev b1)
00:03.3 RAM memory: nVidia Corporation Device 0a89 (rev b1)
00:03.4 RAM memory: nVidia Corporation Device 0a98 (rev b1)
00:03.5 Co-processor: nVidia Corporation Device 0aa3 (rev b1)
00:04.0 USB Controller: nVidia Corporation Device 0aa5 (rev b1)
00:04.1 USB Controller: nVidia Corporation Device 0aa6 (rev b1)
00:06.0 USB Controller: nVidia Corporation Device 0aa7 (rev b1)
00:06.1 USB Controller: nVidia Corporation Device 0aa9 (rev b1)
00:08.0 Audio device: nVidia Corporation Device 0ac0 (rev b1)
00:09.0 PCI bridge: nVidia Corporation Device 0aab (rev b1)
00:0a.0 Ethernet controller: nVidia Corporation MCP79 Ethernet (rev b1)
00:0b.0 IDE interface: nVidia Corporation Device 0ab5 (rev b1)
00:10.0 PCI bridge: nVidia Corporation Device 0aa0 (rev b1)
00:15.0 PCI bridge: nVidia Corporation Device 0ac6 (rev b1)
00:16.0 PCI bridge: nVidia Corporation Device 0ac7 (rev b1)
02:00.0 VGA compatible controller: nVidia Corporation Device 0863 (rev b1)
03:00.0 Network controller: Broadcom Corporation BCM4322 802.11a/b/g/n Wireless LAN Controller (rev 01)
04:00.0 FireWire (IEEE 1394): Agere Systems Device 5901 (rev 07)

Install Media

I decided to put Gentoo (the x86_64 version) on it. So, I grabbed the latest minimal installer (dated 20090618) and booted it. I was disappointed to find that the keyboard didn't work after boot, so I couldn't go anywhere with it. I Googled around a bit, but wasn't able to find anything (except for the possibility of the usbhid driver failing to load due to an unrecognised option, which I thought a bit unlikely). After some head-scratching, I downloaded the install/live CD of Ubuntu 9.04, and booted that. To my happiness, the keyboard was working fine.

rEFIt

Backing up a little... Before booting the Linux CD, first I installed rEFIt from inside MacOS X. I didn't burn the rEFIt bootable CD; I just went ahead and used the Mac installer which did the job without rebooting. After that I could still boot other CDs by holding down the "c" key while booting, but if I didn't, I got a nifty boot menu with MacOS X listed.

Dual Boot

I decided I wanted to keep my MacOS X partition, so I fired up Disk Utility and resized the HFS+ MacOS partition down to 40GB. Yes, amazingly, current Leopard (10.5.7) Disk Utility can resize the startup volume while MacOS is running, without needing to use the installer DVD or reboot at all. It just resizes it live and you're done. It was very quick, too.

BIOS Mode and Partitioning

I've read various sources that said that the proprietary nvidia drivers would not work with the machine in native EFI mode (though this seems to suggest otherwise, but I haven't tried it), so it would have to be booted in BIOS mode. Unfortunately, this makes partitioning the drive a little annoying, as I wanted to keep MacOS X to dual-boot. The GPT partition table layout has a legacy MBR mode for booting BIOS-mode OSes (in our case, grub is the legacy OS).

The MBR compatibility mode only supports four primary partitions, and no extended partitions, so the partition with your kernel on it needs to be one of the first four. Since Apple takes up the first two partition slots (I'm told MacOS X will boot without the first partition, but it's unclear as to whether or not missing that partition will screw up Apple-supplied updates later), that leaves two left. Plenty. Three and four ended up being /boot (96MB), and / (around 60GB). Initially, I was debating leaving out the swap partition, but Linux's suspend to disk (aka hibernate) requires a swap partition, so I allocated 4GB for it. The rest of the space I allocated for /home. I used ext3 for the root and home partitions, since I don't trust ext4 yet, and tools for manipulating ext4 volumes from other OSes are currently nonexistent. For the boot partition I used ext2, and set 'noauto' in the mount options so it doesn't get mounted unless I tell it to. Note that all of these partitions should be primary partitions. GPT doesn't use anything else. Grub will only be able to see my first two Linux partitions (/boot and /), but that's fine, as the kernel will be on /boot, and once Linux comes up it will know how to read the real GPT partition table to find the rest of the partitions.

GPT Resync

After partitioning, I rebooted and selected the GPT/MBR partition table sync item from the rEFIt boot menu. This turned out to be unnecessary for me (I think gparted did it for me?), but this might be needed in some cases.

Gentoo Install

After partitioning, I pretty much followed the normal Gentoo install procedure (downloading a stage3 tarball, unpacking it to the root partition, chrooting, setting up a new kernel and various stuff, installing grub, and rebooting). It was remarkably painless. After the reboot, the rEFIt boot menu had an entry for Linux (with a cute penguin icon) next to the MacOS X entry, so I selected that and booted.

Kernel

I started off with the gentoo-sources-2.6.30-r1 kernel (and just today updated to -r2 with no trouble). Here's my kernel config. I set up the uvesafb framebuffer console driver, which seems to work great.

Video

I installed nvidia-drivers-180.60. Previous versions did not compile against the 2.6.30 kernel. I created a minimal /etc/X11/xorg.conf file that only had 4 lines in it to set the video driver to "nvidia" and let the X server auto-config the rest. It appears to work fine; came up the first time at 1280x800 automatically. I didn't test the external monitor support, as I don't have any DisplayPort devices, and I didn't buy the DVI adapter.

Display Backlight

The backlight didn't work at first, because it looks for specific MBP models, and this new laptop has a newer model number. I simply patched the 'mbp-nvidia-bl' driver (update: see below for a better patch) in the kernel to add "MacBookPro5,5" as a recognised model. However, the backlight controls for some reason do not work while X is running. I suspect the nvidia driver to be at fault here, but more investigation is needed. I'm going to give nouveau a try at some point to see if this fixes any issues (though of course I'll lose 3D accel with that).

Update (2009/07/14): The 'nvclock' program is able to set the backlight brightness while X is running, so I 'ported' the code that does that to the kernel driver. It's not the best patch in the world, but it's here if you want to give it a try. I'm told that this is not really a correct way of fixing the problem, as it's not reasonable to poke at memory addresses owned/reserved by another driver (nvidia, in this case). So expect that some weirdness might occur if you use my patch.

Keyboard Illumination

The keyboard illumination works. The 'applesmc' driver detects it and creates a LED-class device (you can see it in /sys/class/leds). The 'pommed' daemon is supposed to support this, but I haven't tried it yet because it requires patching to support the "MacBookPro5,5" string. Otherwise I can manually set the keyboard lighting via the sysfs interface (just to verify it works). The 'pommed' daemon will monitor the light sensor and adjust the keyboard brightness accordingly. I needed to patch it to get it to recognise the new model string. Apparently upstream already has a patch and support will work in the next release. I also patched it to increase some ridiculously-stupid battery-wasting 200ms timeouts into equally-useful 2s timeouts. Ideally it shouldn't poll at all, but I don't care enough to put in the work to make that happen.

Touchpad

The touchpad works with the 'bcm5974' kernel driver and xf86-input-synaptics X.org driver. Note that to make X.org correctly use the synaptics driver when you aren't providing an xorg.conf file, you need a very recent version of the hal-info package (otherwise it will use the evdev driver). I also have a custom .fdi file to specify some extra options for the driver. Left click works, and 2-finger clicking for right click and 3-finger clicking for middle click work as well. Two-finger horizontal and vertical scrolling work fine. One major annoyance is drag and drop: with MacOS running, I can click and hold with my thumb, and drag with my index finger (while keeping my thumb still). With Linux, I have to click and drag with the same finger, which is very awkward. I filed a feature request about this.

WiFi

The WiFi works, but unfortunately not with the open source 'b43' driver, as that driver does not yet support Broadcom 802.11n chipsets. Broadcom provides a closed-source proprietary driver which seems to work decently, though the speeds I'm getting aren't great, and it has a lot of trouble finding my 5GHz 11n AP sometimes (it finds the 2.4GHz 11g AP just fine though). Udev initially assigned the device the "eth1" name, which I didn't like, so I edited /etc/udev/rules.d/70-persistent-net.rules to change it to "wlan0". NetworkManager works, though intermittently it won't be able to find any APs (despite the fact that "iwlist wlan0 scan" shows APs). I anxiously await support from the 'b43' driver.

When compiling the Broadcom 'wl' driver, be sure to read the README on their website. The compilation is a little weird as they only provide a stub Makefile for use with the kernel's build system. You'll probably want to add 'wl' to /etc/conf.d/modules (or /etc/modules.autoload.d/kernel-2.6) to get loaded on startup. You may want to add 'b43' and 'ssb' to the blacklist in /etc/modprobe.d to be safe. If even the ssb driver gets loaded (even if b43 is not loaded), wl won't work.

One oddity: wl requires the full suite of lib80211 helper modules, including the crypto ones. For some reason these don't get built properly unless you compile another driver into the kernel that needs them, like CONFIG_HOSTAP (you can just compile it as a module).

Bluetooth

The bluetooth chipset is detected, using the standard 'btusb' driver. I haven't tested functionality yet because Bluez 4 is giving me issues.

iSight Camera

The iSight works just fine with the 'uvcvideo' module. Interestingly, on this laptop I did not have to extract the firmware from Apple's MacOS X driver. Oddly, sometimes the driver for it fails to load properly, complaining about not finding a "video chain" or something similar, but removing and reloading the driver usually fixes it (very occasionally a reboot seems to be required).

Sound

I haven't been able to get sound to work yet. The snd-hda-intel driver appears to detect the device, and I can unmute it and change the volume, but playing .wav files using aplay doesn't produce sound. I didn't get any output from the dual-purpose audio in/out jack either. I've tried the patch here, as well as trying ALSA's git repository, but no luck. My current guess is that Apple decided to use a new sound chip. I filed an issue report with ALSA here. My bug hasn't appeared to gain any attention, but someone else's post to alsa-devel has gotten some response, so it looks like this is being worked on.

Update (2009/07/10): There's now experimental support for the Cirrus Logic codec in the hda-cirrus branch of Takashi Iwai's sound-unstable-2.6 tree. I imagine this'll end up in 2.6.31 or .32. I haven't tried it yet, but it's been reported to work on the MacBookPro5,5 (and was indeed the motivation for writing the codec support).

Update (2009/07/12): The new driver works for me, though the sound-unstable-2.6 tree is based on 2.6.31rc1 as of this writing, so it's a little bit of a pain because neither the Broadcom wl driver nor the nvidia binary driver compile correctly against it. I whipped up a couple patches and submitted them to Gentoo's bug tracker (for broadcom-sta, for nvidia-drivers), so hopefully they'll get included in the portage tree soon (and presumably added to future releases). Here's my new kernel config; you just need hda-intel with Cirrus Logic codec support. You could also compile it into the kernel, but I've left it as modules for now.

Suspend/Hibernate

Untested.

Update (2009/07/16): I wish I'd tested this first off, because suspend at least worked perfectly out of the box. No tweaks necessary. I just installed pm-utils and suspended via HAL (using xfce4-power-manager in my case). I imagine calling 'pm-suspend' from the command line would work as well.

Miscellaneous

Shutdown and reboot do not work. The system just halts and locks up. You'll need to hold down the power button for 5 seconds to shut it off. On a rare occasion shutdown will actually power off the unit, but reboot has never worked. I suspect that dropping the BIOS emulation and booting in EFI mode will solve this problem.

USB works fine for mass storage devices, at least. The SD card reader is detected as a usb-storage device, and works as it should.

Firewire drivers load (I used the kernel's "new" firewire stack) and detect the chipset just fine, but I don't have any devices to test it with.

This laptop has an accelerometer (aka Apple Motion Sensor). It appears to work; I can read the machine's orientation via sysfs, but I don't know if there are any daemons that will watch it and park the heads on the hard drive if the laptop gets shaken/dropped. There are also other nifty things you can do with it that I haven't tried.

All things considered, I'm pretty happy. I haven't gotten to a point where I can give meaningful battery life numbers, but I'll try to get to that soon. The machine seems to run pretty cool and quiet, even while compiling for a while. My only major outstanding issue is sound.

BIOS mode vs. EFI mode

In order to get shutdown and reboot working, I've been trying to boot in EFI mode, with no success yet. I've tried the latest SVN versions of grub2, but I've had a lot of trouble. rEFIt only seems to find grub.efi if I put it on the HFS+ MacOS X partition; if put it on the Linux partition, it doesn't work. Running rEFIt's EFI console and poking at the disk partitions seems to indicate that it's failing to read the ext3 partitions for some reason.

Grub2 otherwise just seems very buggy. The config file parsing doesn't appear to work (when grub2 loads, it prints messages about "menuentry" being an invalid command). Despite the fact that linux.mod is loaded, "linux" isn't being recognised as a valid command. In fact, after I use many commands once, successive uses claim that the command is invalid. Even the "help" command doesn't work (fortunately, hitting the tab key lists valid commands). I've mostly given up on grub2. One of the nice things about grub2 is that, if you boot in BIOS mode, you can dump your main and video BIOS images which grub2 can later load/emulate on bootup in EFI mode, which presumably would allow the nvidia drivers to work correctly.

So next I'll be giving elilo a try. It apparently doesn't have support for loading BIOS images, but maybe I can get that to work eventually.

iTunes Protected vs. Unprotected

Dear lazyweb,

A friend of mine has released an album on the iTunes store, and I want to buy it to support her. Of course, I don't want to buy it there if it's a DRM-protected AAC file. Anybody know how you can tell before you buy? I checked both using iTunes on a Mac, and on the iTunes Store app on my iPod Touch, but I can't seem to figure it out.

It seems her album is also sold on Amazon (cheaper, even), and they sell 256kbps MP3s, so I'll probably end up doing that regardless. But it would be nice to know if you can figure this out for Apple's store too.

Update: It appears everything on the iTunes store is now DRM-free. Interesting.

On In-Kernel Drivers

It's pretty well-known that the Linux kernel developers advocate getting your drivers into the main line kernel tree. Doing so ensures that your driver always gets fixed to conform to the latest "correct" internal interfaces when they change, and in-kernel device drivers always get distributed with the kernel, so you get automatic end-user distribution for free.

While I agree with that (without touching the question of whether or not a stable driver binary API/ABI would be useful), I'm not entirely sure it's the best thing in the world for some types of device drivers.

While reading a post by Carl Worth on recent (perceived?) driver stability issues with the Intel video driver for Linux, I came across this bit of insight:

It used to be that getting the latest Intel driver just meant updating an xf86-video-intel module or upgrading an xserver-xorg-video-intel package. But now it's essential to get a recent kernel as well... And these fixes aren't in a major kernel release until 2.6.30 which appeared only today.

This is a problem that perhaps highlights some types of drivers that may be served better by being maintained outside the main line Linux kernel tree. Of course, this is only true while the driver is actively maintained and maintainers are able to quickly update the driver for new kernels (without breaking backward compatibility) as in-kernel interfaces change.

He also notes that distribution maintainers tend to update critical parts of the system (like the kernel) less often than userspace portions (like X.org video drivers). Since the xorg-video-intel driver depends on an in-kernel driver, it's easy for these two things to become out of sync on an end-user's system without testing. Or, frustratingly, the maintainer of the userspace package for a video driver might be unable to update to a newer version of the userspace driver because the kernel packager doesn't wish to upgrade the entire kernel to become compatible with that userspace package (which is an entirely reasonable position to take).

And with more video drivers expected to move toward having significant kernel portions, this problem can only get worse. What if the latest userspace package for drivers A and B both depend on kernel 2.6.30, and the maintainer of package A wants to update, but the package B maintainer doesn't (perhaps there's a bad bug in the latest version of B)? Even if the A package maintainer can convince the kernel maintainer to update, doing so might break things for other users.

Now, sure, having dependency issues like that is nothing new, but I submit that this is a bit different. The kernel contains so much "unrelated" functionality that it has the potential to be a root dependency of many unrelated packages. For example, if a video player depends on a particular version of a codec library, you can often update that codec library without causing issues with other video players (ignoring, for a moment, ffmpeg's inability to maintain a stable API). But updating the kernel because a userspace video driver requires a new kernel version could potentially cause problems with anything ranging from a USB storage device, to a hard disk controller, to an audio card.

Now, of course, the Intel video driver has been undergoing some very large changes lately. In theory, these things will settle down, interfaces will stabilize, and you can expect the tight coupling between the kernel version and userspace package version to relax a bit. But perhaps during this transition period it might make sense to avoid merging kernel code like this at all, and instead do separate releases of (in this case) the DRM modules that are paired to the userspace package. I imagine it wouldn't be too much of a burden for distro packagers to disable building the in-kernel versions of these drivers, and instead ship an extra module package that the userspace driver packages can depend on.

Git Weirdness, Part 2

Ok, now this is just ridiculous:

[brian@machine1 airconfig $] pwd
/home/brian/src/airconfig
[brian@machine1 airconfig $] git branch -a
  advanced-ip-settings
* master
  nm-frontend
  notification-rework
  reconnect
  origin/master
  origin/pre-hal
[brian@machine1 airconfig $] cd .. && mkdir t && cd t
[brian@machine1 t $] git clone ../airconfig
Initialized empty Git repository in /home/brian/src/t/airconfig/.git/
[brian@machine1 t $] cd airconfig
[brian@machine1 airconfig $] git branch -a
* master
  origin/HEAD
  origin/advanced-ip-settings
  origin/master
  origin/nm-frontend
  origin/notification-rework
  origin/reconnect

Ok, that makes sense! Now:

[brian@machine1 airconfig $] cd ..
[brian@machine1 t $] rm -rf airconfig
[brian@machine1 t $]  git clone file:///home/brian/src/airconfig
Initialized empty Git repository in /home/brian/src/t/airconfig/.git/
remote: Counting objects: 1272, done.
remote: Compressing objects: 100% (486/486), done.
remote: Total 1272 (delta 778), reused 1270 (delta 776)
Receiving objects: 100% (1272/1272), 360.40 KiB, done.
Resolving deltas: 100% (778/778), done.
[brian@machine1 t $] cd airconfig
[brian@machine1 airconfig $] git branch -a
* master
  origin/HEAD
  origin/advanced-ip-settings
  origin/master
  origin/pre-hal

What. The. Fuck.

Yes, the git-clone man page tells me that, when using a local pathname (and not using a file: URI), it assumes the other repo is local and uses hardlinks between the repos. But hey, if I specify --no-hardlinks when cloning, I still get all branches if I use the "../airconfig" method.

And if I go to machine2 and do "git clone machine1:src/airconfig" I get the same broken result with the file:// method.

Git, I think you rock. But why does your user interface suck so much? And why do you just appear to be broken right now? I can't seem to find anything else in the man page to help here. (I'm using git 1.6.1.3 if that matters.)

Git Weirdness

So I have a git repo on my machine at home, that's cloned from a repo on git.xfce.org. I was doing some work in it the other day -- in a private branch, not published to git.xfce.org -- and I wanted to continue working on the private branch from another machine. But... it won't work. Let's say 'machine1' has the repo with the private branch, and 'machine2' is where I want to work today.

So, on machine2, I cloned from the master repo on git.xfce.org:

[brian@machine2 src $] git clone git@git.xfce.org:kelnos/airconfig
[... stuff happens...]
[brian@machine2 src $] cd airconfig
[brian@machine2 airconfig $] git branch -a
* master
  origin/master
  origin/pre-hal

Ok, cool, that's what I expect. So I ssh over to machine1 (the one I eventually want to pull from), and I check out my list of branches there:

[brian@machine1 airconfig $] git branch -a
  advanced-ip-settings
  master
* nm-frontend
  notification-rework
  reconnect
  origin/master
  origin/pre-hal

Ok, cool. the 'nm-frontend' branch is the one I want to pull to machine2. So on machine2, I do this:

[brian@machine2 airconfig $] git remote add machine1 machine1:src/airconfig
[brian@machine2 airconfig $] git pull machine1 nm-frontend
fatal: Couldn't find remote ref nm-frontend
fatal: The remote end hung up unexpectedly

Uh... what? Do I have the wrong syntax? Ok, let me just try to pull everything from the remote:

[brian@machine2 airconfig $] git pull machine1
remote: Counting objects: 1085, done.
remote: Compressing objects: 100% (301/301), done.
remote: Total 1085 (delta 774), reused 1085 (delta 774)
Receiving objects: 100% (1085/1085), 323.43 KiB | 14 KiB/s, done.
Resolving deltas: 100% (774/774), done.
From machine1:src/airconfig
 * [new branch]      advanced-ip-settings -> machine1/advanced-ip-settings
 * [new branch]      master     -> machine1/master
 * [new branch]      pre-hal    -> machine1/pre-hal

And then at the bottom it prints out a message about not knowing which local branches to merge stuff into. That's fine, no big deal. But... how come it pulled 3 of my local branches on machine1, but left off 2 of them ('notification-rework' and 'nm-frontend'). No combination of src:dest refspecs seem to do the trick. Pulling one of the 3 branches it seems to like using the syntax I used above seems to work fine, but it can't see the one I want. What am I doing wrong...?

Xfce 4.6.0 Released

I'm a bit late with this, but we finally got Xfce 4.6.0 out the door. Others have written much more about this than I have, so feel free to read about it on our blog aggregator.

Ruby for Web Development

I recently started a new web dev project, and decided to use it to better learn Ruby. However, I don't want to use Rails. I'd like to keep it simple. I also prefer to know a lot about the inner workings of a particular technology before I go and use a large framework that hides a bunch of details from me.

However, I want to use ActiveRecord. ORM seems to be all the rage these days, promising to abstract the annoying details of database access behind OO natural for your chosen language. It also helps avoid common errors and pitfalls with regard to constructing SQL queries and the like.

So, ActiveRecord. I install it on my laptop with "emerge ruby-activerecord", and there I go. One "require 'activerecord'" in my script later, and, awesome, it starts working.

Then I start working on my web host (DreamHost, if you're wondering). It can't find ActiveRecord. But it's obviously installed, because I know DH supports Rails out of the box, and I don't think you can have an install of Rails without ActiveRecord. So I poke, and then realise it might be installed as a Ruby "gem." Ok, so I put a "require 'rubygems'" above my activerecord require. Nice, now it works. Then I think, well, what if I put this somewhere that doesn't require rubygems? Not hard to work around automatically:

``begin
  require 'activerecord'
rescue LoadError
  require 'rubygems'
  require 'activerecord'
end``

Nice, ok, that works. It's probably a foolish micro-optimisation, but whatever.

Then I notice... ugh, this is super slow. Even on the web host, it can take a good two seconds for the "require 'activerecord'" statement to execute. Yeah, I know, ruby is kinda slow. But 2 extra seconds each time someone hits basically any page of the website? Ugh.

So.... FastCGI. I know DH supports it, so I head over to the control panel and enable it for the domain I'm working on, and start googling around to figure out how to use FastCGI in a ruby script.

Unfortunately, there aren't too many resources on this. Fortunately I found a couple sample dispatch scripts, one of which I ended up basing mine off of.

But then there was a problem. Inside my app, I use ruby's CGI class to access CGI form variables and other stuff. Since the FastCGI stuff overrides and partially replaces ruby's internal CGI class, there's a problem. Doing "cgi = CGI.new" inside a ruby script that's being served through FastCGI throws a weird exception. But I wanted to try to retain compatibility for non-FastCGI mode. And I couldn't figure out how to get the 'cgi' variable from the ruby dispatch script into my app's script, since I was using 'eval' to run my script. The dispatch script I saw used some weird Binding voodoo. Up at the top level we have:

``def getBinding(cgi, env)
  return binding
end``

I had no idea what that was doing, so I looked it up. Apparently the built-in "binding" function returns a Binding object that describes the current execution context, including local variables and function/method arguments. Ok, that seems really powerful and cool. So I look down to the sample dispatch script's eval statement, and I see:

``eval File.open(script).read, getBinding(cgi, cgi.env_table)``

Ok, so it appears it tries to eval the script while providing an execution context that contains just 'cgi' and one of its member vars. I only sorta understand this. So I ditched the "cgi = CGI.new" line in my app's script. But I got a NameError when just trying to use 'cgi'. Huh? What's going on? So I get rid of the getBinding() call entirely, and just let it use the current execution context, and suddenly everything works right. Weird.

Well, sorta. Now, remember, I want to preserve compatibility with running as a normal CGI. So the normal CGI needs to create its own 'cgi' object, but the FastCGI one should just use the one from the dispatch script. So I came up with this:

``begin
  if !cgi.nil?
    mycgi = cgi
  end
rescue NameError
  require 'cgi'
  mycgi = CGI.new
end``

Ok, that seemed to work ok. After that block, 'mycgi' should be usable as a CGI/FCGI::CGI object regardless of which mode it's running under.

So I play around a bit more, and suddenly notice that my POST requests have stopped working. I dig into it a bit, and realise that my POST requests are actually just fine. What's happening is that, somehow, the FCGI::CGI object completely ignores $QUERY_STRING on a POST request, while ruby's normal CGI object will take care of it and merge it with the POST data variables. You see, to make my URLs pretty, I have normal URLs rewritten such that the script sees "page=whatever" in the query string. So when I did a POST, the page= would get lost, and so the POST would end up fetching the root web page rather than the one that should be receiving the form variables. I'm not sure if this is "normal" behavior, or if the version of the fcgi ruby module on DreamHost has a bug. Regardless, we need a workaround. So I go back to my last code snippet, and hack something together:

``begin
  if !cgi.nil?
    if cgi.env_table['REQUEST_METHOD'] == 'POST'
      CGI.parse(cgi.env_table['QUERY_STRING']).each do |k,v|
        cgi.params[k] = v
      end
    end
    mycgi = cgi
  end
rescue NameError
  require 'cgi'
  mycgi = CGI.new
end``

Ick. But at least it works.

So far, I'm liking ruby quite a lot. It's a beautiful language, and seems well-suited for this kind of work, especially since I want to get something that works up and running relatively quickly.

We'll see, however, how many more gotchas I run into.

Licensing Suckage

I just got an email from a developer who works on the nifty cairo-dock application, pointing me to a thread about licensing issues.

A bunch of months ago, he'd emailed me asking about how to best use code from my Xfce Mailwatch Plugin in cairo-dock to add mail-checking capabilities. At the time, I was pretty stoked that someone else had actually found my code useful enough to incorporate into their program, and offered my encouragement.

Sadly, though, licensing ugliness has reared its... well... ugly... head.

When licensing code under the terms of the GNU GPL or LGPL, the FSF suggests (and most people follow) that you license under "or (at your option) any later version" terms, which means that, while you initially license the code under the version of the GPL or LGPL of your choice, someone can later take your code and relicense it under the terms of a later version of the same license. This also makes the code automatically compatible with future versions of the license.

You might think this sounds pretty good for convenience and licensing compatibility, and you'd probably be right.

However, this isn't so great from a philosophical perspective, at least from my philosophical perspective. The problem I have is this:

Licensing a work under "GPL version 2 or later" terms means that I am implicitly agreeing with any new restrictions that the FSF dreams up (or any existing restrictions the FSF wants to drop), forever. I'd basically be saying that I agree with something that doesn't exist yet, and could take any shape or form imaginable.

Don't get me wrong: in general, I think the FSF is good people, and I agree with their message for the most part.

But I don't know them, personally, and I don't agree with them 100%. And I don't know who's going to be running the FSF next year, or in five years, or in 20 years. So how can I know, or even have reasonable belief, that their philosophies and values will align with mine such that I'll agree with future versions of their licenses? There are already parts of version 3 of the GPL and LGPL that I don't completely understand or agree with, so why should I expect that versions 4, 5, or 10 will be completely to my liking?

The short answer is: I can't.

And so, for the most part, I release my software under "GPL version 2 only" terms. (Because I'm a bit lazy and don't want to make a big stink, I'll release code under "or any later version" terms if I'm contributing to an existing code base that uses those terms.)

it really pained me to have to answer that email saying that my code's licensing (GPLv2-only) wasn't compatible with theirs (GPLv3-or-later), but it's the truth, and there's not much I can (or want to) do about it.

The only solution I can think of (I'm not a lawyer, of course) that allows them to use my code is that they relicense their code under GPLv2-or-later terms. Of course, then they lose any restrictions that the GPLv3 has over the GPLv2, which I assume they'd prefer to have, since that's how they've licensed their code.

(Before anyone says it, another possible solution would be for me to relicense under LGPLv2.1. The problem with that is one I've discussed before: section 3 of the LGPLv2.1 explicitly allows a recipient of the code to relicense the code under regular GPLv#-or-later terms, regardless of the only/or-later status of the original LGPL licensing. This of course completely defeats the intent of my rationale above.)

And so, the OSS licensing mess has caused yet more pain to people who just want to share code and avoid duplicating effort. I love the GPL. I really do. But I also hate it.

« prev 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 next »