Tag Archives: api

libnbd 0.9.8 and stable APIs

I announced libnbd yesterday. The libnbd 0.9.8 is a pre-release for the upcoming 1.0 where we will finalize the API and offer API and ABI stability.

Stable APIs aren’t in fashion these days, but they’re important because people who choose to use your platform for their software shouldn’t be screwed over and have to change their software every time you change your mind. In C it’s reasonably easy to offer a stable API while allowing long term evolution and even incompatible changes. This is what we do for nbdkit and will be doing for libnbd.

The first concept to get to know is ELF symbol versioning. Chapter 3 of Uli’s paper on the subject covers this in great detail. In libnbd all our initial symbols will be labelled with LIBNBD_1.0.

The second concept is not as well known, but is used in glibc, nbdkit and elsewhere: Allowing callers to opt in to newer versions of the API. Let’s say we want to add a new parameter to nbd_connect_uri. The current definition is:

int nbd_connect_uri (struct nbd_handle *h, const char *uri);

but we decide to add flags:

int nbd_connect_uri (struct nbd_handle *h, const char *uri,
                     uint32_t flags);

You can cover up the ABI change with ELF symbol versions. However the API has still changed, and you’re forcing your callers to change their source code eventually. To prevent breaking the API, you can allow callers to opt in to the change by defining a new symbol:

#define LIBNBD_API_VERSION 2
#include <libnbd.h>

The definition is saying “I want to upgrade to the new version and I’ve made the required changes”, but crucially if the definition is missing or has an earlier version number then you continue to offer the earlier version, perhaps with a simple wrapper that calls the new version with a default flags value.

That’s what we intend to do with libnbd to offer a stable API and ABI (once 1.0 is released) without breaking callers either at the source or binary level.

1 Comment

Filed under Uncategorized

Tip: libguestfs API: Get the mounted device from a path

This useful libguestfs API tip shows you how to get the device name that contains a mounted path. You can use this if you want to find out the filesystem type of a path (eg. is this directory mounted on ext4?).

What you do is call guestfs_mountpoints which returns a hash of device name to mount point, eg:

/dev/sda1 -> /boot
/dev/sda2 -> /
/dev/sda3 -> /usr

Then compare the pathname (“/boot/grub”) to each entry in the hash. If the mountpoint is a string prefix of the path, give this entry a score which is the length of the mountpoint string. If it is not a prefix, give the entry a score 0. So:

/dev/sda1 -> /boot (score: 5)
/dev/sda2 -> / (score: 1)
/dev/sda3 -> /usr (score: 0)

Then sort the entries to pick the highest score. If the hash is empty or the highest score is 0, then return an error, otherwise return the device with the highest score.

Here is the code to implement this in OCaml:

open Printf
open ExtString
open ExtList

let get_mounted_device g path =
  let mps = g#mountpoints () in
  let mps = List.map (
    fun (dev, mp) ->
      if String.starts_with path mp then
        dev, String.length mp
      else
        dev, 0
  ) mps in
  let cmp (_,n1) (_,n2) = compare n2 n1 in
  let mps = List.sort ~cmp mps in
  match mps with
  | [] ->
      invalid_arg (sprintf "%s: not mounted" path)
  | (_,0) :: _ ->
      invalid_arg (sprintf "%s: not found on any filesystem" path)
  | (dev,_) :: _ -> dev

To answer the question “is this directory mounted on ext4?” you would then call guestfs_vfs_type on the result of this, eg:

if g#vfs_type (get_mounted_device g "/boot/grub") = "ext4" then
  (* do something based on ext4 *)

Leave a comment

Filed under Uncategorized

Example: using the libguestfs API from C

I spent about the last 18 hours contributing a section on libguestfs to the RHEL 6 Virtualization Guide. There is precisely zero in the guide at the moment on this subject, but hopefully there will be lots when RHEL 6.1 is released.

You might have forgotten that libguestfs is really a C library that just happens to have a lot of high level wrappers around it. But you can use the C API directly and (for a C API) it’s not too hard.

Below is the example that will appear in the Virtualization Guide next year. You can compile and run the program by saving it to a file test.c and typing:

gcc -Wall test.c -o test -lguestfs
./test

After you run it, there will be a disk image in the current directory called disk.img which you can view using guestfish:

guestfish -a disk.img -m /dev/sda1

For more information about the libguestfs C API, read the API overview section of the documentation.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <guestfs.h>

int                                                                            
main (int argc, char *argv[])
{
  guestfs_h *g;
  size_t i;

  g = guestfs_create ();
  if (g == NULL) {
    perror ("failed to create libguestfs handle");
    exit (EXIT_FAILURE);
 }

  /* Create a raw-format sparse disk image, 512 MB in size. */
  int fd = open ("disk.img", O_CREAT|O_WRONLY|O_TRUNC|O_NOCTTY, 0666);
  if (fd == -1) {
    perror ("disk.img");
    exit (EXIT_FAILURE);
  }
  if (ftruncate (fd, 512 * 1024 * 1024) == -1) {
    perror ("disk.img: truncate");
    exit (EXIT_FAILURE);
  }
  if (close (fd) == -1) {
    perror ("disk.img: close");
    exit (EXIT_FAILURE);
  }

  /* Set the trace flag so that we can see each libguestfs call. */
  guestfs_set_trace (g, 1);

  /* Set the autosync flag so that the disk will be synchronized
   * automatically when the libguestfs handle is closed.
   */
  guestfs_set_autosync (g, 1);

  /* Add the disk image to libguestfs. */
  if (guestfs_add_drive_opts (g, "disk.img",
        GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", /* raw format */
        GUESTFS_ADD_DRIVE_OPTS_READONLY, 0, /* for write */
        -1) /* this marks end of optional arguments */
      == -1)
    exit (EXIT_FAILURE);

  /* Run the libguestfs back-end. */
  if (guestfs_launch (g) == -1)
    exit (EXIT_FAILURE);

  /* Get the list of devices.  Because we only added one drive
   * above, we expect that this list should contain a single
   * element.
   */
  char **devices = guestfs_list_devices (g);
  if (devices == NULL)
    exit (EXIT_FAILURE);
  if (devices[0] == NULL || devices[1] != NULL) {
    fprintf (stderr, "error: expected a single device from list-devices\n");
    exit (EXIT_FAILURE);
  }

  /* Partition the disk as one single MBR partition. */
  if (guestfs_part_disk (g, devices[0], "mbr") == -1)
    exit (EXIT_FAILURE);

  /* Get the list of partitions.  We expect a single element, which
   * is the partition we have just created.
   */
  char **partitions = guestfs_list_partitions (g);
  if (partitions == NULL)
    exit (EXIT_FAILURE);
  if (partitions[0] == NULL || partitions[1] != NULL) {
    fprintf (stderr, "error: expected a single partition from list-partitions\n");
    exit (EXIT_FAILURE);
  }

  /* Create a filesystem on the partition. */
  if (guestfs_mkfs (g, "ext4", partitions[0]) == -1)
    exit (EXIT_FAILURE);

  /* Now mount the filesystem so that we can add files. */
  if (guestfs_mount_options (g, "", partitions[0], "/") == -1)
    exit (EXIT_FAILURE);

  /* Create some files and directories. */
  if (guestfs_touch (g, "/empty") == -1)
    exit (EXIT_FAILURE);
  const char *message = "Hello, world\n";
  if (guestfs_write (g, "/hello", message, strlen (message)) == -1)
    exit (EXIT_FAILURE);
  if (guestfs_mkdir (g, "/foo") == -1)
    exit (EXIT_FAILURE);

  /* This one uploads the local file /etc/resolv.conf into the disk image. */
  if (guestfs_upload (g, "/etc/resolv.conf", "/foo/resolv.conf") == -1)
    exit (EXIT_FAILURE);

  /* Because 'autosync' was set (above) we can just close the handle
   * and the disk contents will be synchronized.  You can also do
   * this manually by calling guestfs_umount_all and guestfs_sync.
   */
  guestfs_close (g);

  /* Free up the lists. */
  for (i = 0; devices[i] != NULL; ++i)
    free (devices[i]);
  free (devices);
  for (i = 0; partitions[i] != NULL; ++i)
    free (partitions[i]);
  free (partitions);

  exit (EXIT_SUCCESS);
}

Leave a comment

Filed under Uncategorized

Easy introduction to the libguestfs API

Baffled by the 269 calls that libguestfs provides? Read the libguestfs API overview.

Also: Internet News interviews Paul Frields about Fedora 12.

Update: Another favourable F12 article at arstechnica.

Leave a comment

Filed under Uncategorized