The grim reality of C

In the last few years, I haven't had cause to write much C code; just enough to stop me getting rusty. I remain very fond of the C programming language, and sometimes I find myself thinking that programming in C has its advantages, despite the need to pay attention to a few details that are not an issue in higher-level languages. Maybe C is not so bad. Maybe, if I had to tackle a project with the right set of requirements, I'd work in C again.

But coming into contact with C code can be like reading about surgery before anaesthetic: The unwelcome and painful realization that people had to live like that for so long, and that in some primitive parts of the world, they still do.

Although I don't get to write much C code, I do still regularly refer to C code written by others. But there's a depth of understanding of a language and its milieu that can only be gained by writing a significant amount of code in that language, or making significant changes to an existing project. Today, I've been doing the latter.

The project in question is a relatively successful open source project, of a non-trivial size (150k lines). The leaders and main contributors appear to be competent and experienced, and have made sensible design decisions. After a superficial review of the code, my response was quite positive. It adheres to a range of uncontroversial coding standards. It is consistently formatted. And it uses utility functions where appropriate to handle error prone tasks safely. In particular, it avoids the direct use of malloc and friends, and instead uses a set of helper macros and functions for allocation and deallocations of common data structures and arrays.

But after a few hours of working on this codebase, I began to realize that it is riddled with allocation and deallocation bugs. The helpers help, but not enough. The consequence of the smallest confusion over the ownership of some data structure is a memory leak.

I'm not going to name the project, because I continue to believe that its overall quality is very good, and that the developers have tried hard to get these issues right. What it brings home to me is a fact that I haven't been forced to face for a long time: while bad programmers can write bad code in any language, even good programmers find it hard to write good, bug-free code in C.

2 comments

USB pass-through with libvirt and KVM, part two

A recent post here discussed how to enable USB pass-through under libvirt. But the technique there only allowed devices to be added when a VM was next started; you couldn't connect devices to a running VM.

This is a fairly major limitation. At best, the need to restart the VM is inconvenient. At worst, it makes the use of a USB device impossible. The full use of many USB devices involves disconnecting and reconnecting them to a running machine.

Recent versions of QEMU (and so KVM) do in fact have support for this; it's just that libvirt doesn't expose it. The QEMU support involves marking USB devices (identified by a vendor/product ID pair) for autoconnect. QEMU will then listen for connection events from the host OS, and repond to connections from the relevant devices by signalling a connection of the pass-through device to an emulated USB hub within the VM.

When libvirt runs KVM/QEMU, it specifies the pass-through USB devices through command line options. So by introducing a wrapper script that rewrites the command line options, we can enable autoconnect under libvirt.

Note that you will need a recent version of the KVM userspace support for this to work. It works for me with kvm-84. kvm-74, as included in Fedora 10 and Ubuntu 8.10 is not recent enough.

Here is the script I use:

#!/bin/sh
exec /usr/bin/qemu-kvm `echo $* | sed 's|-usbdevice host:\([^ ]*\)|-usbdevice host:auto:*.*:\1|g'`

Note that different Linux distributions use slightly different names for the KVM/QEMU binary. /usr/bin/qemu-kvm above is correct for RHEL/Fedora systems; under Ubuntu, you should substitute /usr/bin/kvm.

Save the script as /etc/libvirt/qemu/qemu-kvm, and make it executable. With that in place, you need to tell libvirt to use it instead of the real KVM binary. Do that by editing the VM XML description as described in my previous post. You need to edit the //domain/devices/emulator entry to refer to the wrapper script, e.g.

<domain type='kvm'>
  <name>windowsxp</name>
  …
  <devices>
    <emulator>/etc/libvirt/qemu/qemu-kvm</emulator>
    …
  </devices>
</domain>

Restart the relevant VMs, and USB pass-through with autoconnect should now work.

It's tempting to ask why this functionality isn't built-in to libvirt. My impression is that they are aiming for something more ambitious: The ability to enumerate devices on the host and then selectively pass those through to running VMs. This will be good when it's done, it's just a shame that it isn't there yet.

8 comments

Blogging for The Man

I have a blog post up on the company blog. But it's not about work (well, not work work), so if you are one of my small band of regular readers here, you should take a look.

Using USB pass-through under libvirt and KVM

Virtualization solutions typically include a feature called USB pass-through: making a USB device attached to the host machine appear directly as a USB device attached to a virtual machine. KVM, the fully open-source virtualization solution for Linux, can do USB pass-through. It inherits this feature from QEMU, which KVM incorporates to provide system and device virtualization (KVM proper is focused on processor virtualization, and runs in kernel space; QEMU runs in user space).

But there's a hitch: KVM is most often used via libvirt and virt-manager, which provide a virtualization management infrastructure and graphical user interface. libvirt has some support for USB pass-through; but virt-manager doesn't support it at all. So here's a guide to using the libvirt command shell to get to this feature. These instructions require somewhat recent versions of KVM (74 and up) and libvirt (0.4.4 and up).

You will need to hand-edit the XML documents that libvirt uses to describe virtual machine instances (domains, in libvirt terminology). You do this via virsh, the command shell included with libvirt. virsh is fairly spartan, though it does have a decent on-line help facility (accessed via the help command). Run virsh as root, and use the list command to see your VM instances and their names:

virsh # list --all
 Id Name                 State
----------------------------------
  - windowsxp            shut off

If list --all didn't show any VM instances, you should create some using virt-manager.

You can view the domain definition XML with the dumpxml command:

virsh # dumpxml windowsxp
<domain type='kvm'>
  <name>windowsxp</name>
  …
</domain>

The edit command will open the domain definition XML in an editor:

virsh # edit windowsxp
Edit the XML as desired
Domain windowsxp XML configuration edited.

Update: I recently noticed that the edit command only appeared in libvirt version 0.4.5. Some current editions of popular Linux distributions still have an earlier version. In that case, you can edit the XML domain definition files under /etc/libvirt/qemu directly, but you will need to restart libvirtd afterwards.

We want to edit this XML to add a hostdev element as documented here on the libvirt site. But first, you need to work out the USB vendor and product IDs for the relevant device. You can discover these by attaching the device and running /sbin/lsusb on the host. For example, here's the lsusb output on my laptop:

$ /sbin/lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 005 Device 012: ID 0a5c:2110 Broadcom Corp. Bluetooth Controller
Bus 005 Device 003: ID 0483:2016 SGS Thomson Microelectronics Fingerprint Reader
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 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

The pairs of hexadecimal numbers on each line are the vendor and produce IDs. For example, the bluetooth controller has a vendor ID of 0a5c, and a product ID of 2110.

To forward that to my windowsxp VM, I edit the domain definition as follows:

<domain type='kvm'>
  <name>windowsxp</name>
  …
  <devices>
    …
    <hostdev mode='subsystem' type='usb'>
      <source>
        <vendor id='0x0a5c'/>
        <product id='0x2110'/>
      </source>
    </hostdev>
  </devices>
</domain>

Note that it's a good idea to prevent the host from using the device before attaching it to a VM. In this case, I made sure the bluetooth controller was not being used by removing the relevant kernel modules with rmmod. For USB storage devices, it's sufficient to make sure that they are not mounted by the host kernel. And for custom USB devices, without drivers in the Linux kernel, it won't try to do anything with them, so you're usually fine.

There are a couple of limitations to this USB pass-through support. The first is that the change won't take effect until you next start the VM. The second is that it only works if the USB device in question is connect at the time you start the VM. That's quite a serious restriction. It's often convenient, and sometimes necessary, to be able to connect a device to, and disconnect it from, a running machine. In a follow-up post, I'll describe a way to avoid this limitation. It's now up here.

But once it's in operation, this USB pass-through support does work well. I've used it with a range of USB devices, with complete success.

3 comments

@dwragg

I've started twittering recently. If you've always wanted to follow what I was having for breakfast, now you can!

(For your information, it's usually Weetabix. That was the main thing I missed when I was abroad for five years.)

Recent kvm versions need kernel 2.6.25 or later

The kvm download page mentions that versions 76 onwards need kernel 2.6.25. What it doesn't mention is what happens if you overlook this requirement. Which is that VMs will hang on the BIOS screen, just after the “Press F12 for boot menu” prompt. You don't get any kind of error message anywhere, so searching the web for help doesn't reveal much. In other words, the kind of issue you could easily bang your head against for a couple of hours.