Richard W.M. Jones
2009-Aug-03 16:06 UTC
[Libguestfs] [PATCH] New commands to list devices by UUID and label
This isn't very well tested at the moment because my main testing machine is broken. So just for comment, only to be applied with caution. This patch has two new commands which make it much easier to look up filesystems by their UUID or label. They are: list-devices-by-uuid list-devices-by-label Each returns a hashtable (or whatever the equivalent structure is in your favourite programming language). The keys of the hash are UUID or label. The values are the filesystem device, eg. /dev/sda1 or /dev/VG/LV Rich. -- Richard Jones, Emerging Technologies, Red Hat http://et.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 75 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora -------------- next part -------------->From d045b5f8d0389042db48c1078e417a29cb0029ad Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Mon, 3 Aug 2009 11:44:51 +0100 Subject: [PATCH] New commands: list-disks-by-uuid and list-disks-by-label These commands returned a hash from UUID/label -> device, making it simpler to locate partitions which have UUIDs or labels. --- daemon/Makefile.am | 7 +- daemon/deviceby.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + src/MAX_PROC_NR | 2 +- src/generator.ml | 16 +++ 5 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 daemon/deviceby.c diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 024c097..0fec611 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -28,6 +28,7 @@ guestfsd_SOURCES = \ cpmv.c \ daemon.h \ debug.c \ + deviceby.c \ devsparts.c \ df.c \ dir.c \ @@ -74,6 +75,8 @@ guestfsd_SOURCES = \ zero.c \ zerofree.c \ $(top_builddir)/../src/guestfs_protocol.h \ - $(top_builddir)/../src/guestfs_protocol.c + $(top_builddir)/../src/guestfs_protocol.c \ + $(srcdir)/../.gnulib/lib/hash.h \ + $(srcdir)/../.gnulib/lib/hash.c -guestfsd_CFLAGS = -Wall +guestfsd_CFLAGS = -Wall -I$(srcdir)/../.gnulib/lib diff --git a/daemon/deviceby.c b/daemon/deviceby.c new file mode 100644 index 0000000..2006442 --- /dev/null +++ b/daemon/deviceby.c @@ -0,0 +1,295 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "daemon.h" +#include "actions.h" + +#include "hash.h" /* Gnulib hash table. */ + +/* Hash used in the first pass. */ +struct h1 { + dev_t dev; /* device */ + char *dname; /* d_dname entry (ie. UUID or label) */ +}; + +static size_t +hasher1 (const void *h1v, size_t table_size) +{ + const struct h1 *h1 = h1v; + + return (unsigned) h1->dev % table_size; +} + +static bool +comparator1 (const void *h11v, const void *h12v) +{ + const struct h1 *h11 = h11v; + const struct h1 *h12 = h12v; + + return h11->dev == h12->dev; +} + +static void +freer1 (void *h1v) +{ + struct h1 *h1 = h1v; + + free (h1->dname); + free (h1); +} + +/* Note that d->d_name can contain \x<X><X> sequences which + * we must replace with a character (eg. "\x2f" -> "/") + * + * How escaping really works in udev is very unclear, so this + * code is at best a guess. + */ +static int +getxdigit (char c) +{ + switch (c) { + case '0'...'9': return c - '0'; + case 'a'...'f': return c - 'a' + 10; + case 'A'...'F': return c - 'A' + 10; + default: abort (); + } +} + +static char * +unescape (const char *dname) +{ + int i, j, len; + char *r; + + len = strlen (dname); + r = malloc (len + 1); + if (r == NULL) { + reply_with_perror ("malloc"); + return NULL; + } + + for (i = j = 0; i < len; ++i, ++j) { + if (i < len-4 && /* \xAB */ + dname[i] == '\\' && dname[i+1] == 'x' && + isxdigit (dname[i+2]) && isxdigit (dname[i+3])) { + r[j] = getxdigit (dname[i+2]) * 16 + getxdigit (dname[i+3]); + i += 3; + } + else if (i < len-2 && dname[i] == '\\') { /* \\ etc */ + r[j] = dname[i+1]; + i++; + } + else + r[j] = dname[i]; + } + + r[j] = '\0'; + + return r; +} + +static char ** +list (const char *udevpath) +{ + DIR *dir = NULL; + struct dirent *d; + char path[PATH_MAX]; + struct stat statbuf; + Hash_table *hash1 = NULL; + char **ret = NULL; + int size = 0, alloc = 0; + + /* /dev/disk is populated by udev */ + udev_settle (); + + dir = opendir (udevpath); + if (!dir) { + /* If there are no labels/UUIDs then udev sometimes won't + * even create this directory. In this case let's return + * an empty hashtable. + */ + perror (udevpath); + if (add_string (&ret, &size, &alloc, NULL) == -1) + return NULL; + return ret; + } + + /* First pass, over the udev /dev/disk directory. */ + hash1 = hash_initialize (128, NULL, hasher1, comparator1, freer1); + if (hash1 == NULL) { + reply_with_perror ("hash_initialize"); + goto error; + } + + while ((d = readdir (dir)) != NULL) { + snprintf (path, sizeof path, "%s/%s", udevpath, d->d_name); + if (stat (path, &statbuf) == -1) { /* Stat the target of the link. */ + perror (path); + continue; + } + /* Ignore ".", "..", and any other non-block devices. */ + if (!S_ISBLK (statbuf.st_mode)) + continue; + + /* Now add a hash entry rdev -> d_name. We'll look up the + * device name in the second pass. + */ + struct h1 *h1 = malloc (sizeof *h1); + if (h1 == NULL) { + reply_with_perror ("malloc"); + goto error; + } + h1->dev = statbuf.st_rdev; + h1->dname = unescape (d->d_name); + if (!h1->dname) { + free (h1); + goto error; + } + if (hash_insert (hash1, h1) == NULL) { + reply_with_perror ("hash_insert"); + free (h1); + goto error; + } + } + + if (closedir (dir) == -1) { + reply_with_perror (udevpath); + dir = NULL; + goto error; + } + + /* In the second pass we look at all devices in /dev and /dev/mapper + * and try to find matching device major/minor numbers for them in + * the hash. + */ + dir = opendir ("/dev"); + if (!dir) { + reply_with_perror ("/dev"); + goto error; + } + + while ((d = readdir (dir)) != NULL) { + snprintf (path, sizeof path, "/dev/%s", d->d_name); + if (stat (path, &statbuf) == -1) { + perror (path); + continue; + } + + /* st_rdev is in the hash? If so, it's an entry in the result. */ + struct h1 h1, *r; + h1.dev = statbuf.st_rdev; + r = hash_lookup (hash1, &h1); + if (r) { + /* Key: UUID or label */ + if (add_string (&ret, &size, &alloc, r->dname) == -1) + goto error; + /* Value: /dev/... */ + if (add_string (&ret, &size, &alloc, path) == -1) + goto error; + } + } + + if (closedir (dir) == -1) { + reply_with_perror (udevpath); + free_strings (ret); + dir = NULL; + goto error; + } + + dir = opendir ("/dev/mapper"); + if (!dir) { + reply_with_perror ("/dev/mapper"); + goto error; + } + + while ((d = readdir (dir)) != NULL) { + snprintf (path, sizeof path, "/dev/mapper/%s", d->d_name); + if (stat (path, &statbuf) == -1) { + perror (path); + continue; + } + + /* st_rdev is in the hash? If so, it's an entry in the result. */ + struct h1 h1, *r; + h1.dev = statbuf.st_rdev; + r = hash_lookup (hash1, &h1); + if (r) { + /* Key: UUID or label */ + if (add_string (&ret, &size, &alloc, r->dname) == -1) + goto error; + /* Value needs to be rewritten from /dev/mapper/VG-LV to /dev/VG/LV. */ + char *p = strchr (d->d_name, '-'); + if (p) { + *p++ = '\0'; + snprintf (path, sizeof path, "/dev/%s/%s", d->d_name, p); + } + + if (add_string (&ret, &size, &alloc, path) == -1) + goto error; + } + } + + if (closedir (dir) == -1) { + reply_with_perror (udevpath); + free_strings (ret); + dir = NULL; + goto error; + } + dir = NULL; + + hash_free (hash1); + hash1 = NULL; + + /* Terminate the returned structure. */ + if (add_string (&ret, &size, &alloc, NULL) == -1) + goto error; + + return ret; /* caller frees */ + + error: + if (dir) + closedir (dir); + if (hash1) + hash_free (hash1); + + return NULL; +} + +char ** +do_list_devices_by_uuid (void) +{ + return list ("/dev/disk/by-uuid"); +} + +char ** +do_list_devices_by_label (void) +{ + return list ("/dev/disk/by-label"); +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 2a57823..3853d4a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -6,6 +6,7 @@ daemon/cmp.c daemon/command.c daemon/cpmv.c daemon/debug.c +daemon/deviceby.c daemon/devsparts.c daemon/df.c daemon/dir.c diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR index dc37bbd..bc3d544 100644 --- a/src/MAX_PROC_NR +++ b/src/MAX_PROC_NR @@ -1 +1 @@ -184 +186 diff --git a/src/generator.ml b/src/generator.ml index b6f6f42..f2d8cfe 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -3393,6 +3393,22 @@ This closes the inotify handle which was previously opened by inotify_init. It removes all watches, throws away any pending events, and deallocates all resources."); + ("list_devices_by_uuid", (RHashtable "disks", []), 185, [], + [], + "map of UUIDs to device names", + "\ +This call returns a hash table where the keys are the +UUIDs of all filesystems found, and the values are the +device, partition or LV name where the filesystem is located."); + + ("list_devices_by_label", (RHashtable "disks", []), 186, [], + [], + "map of labels to device names", + "\ +This call returns a hash table where the keys are the +labels of all filesystems found, and the values are the +device, partition or LV name where the filesystem is located."); + ] let all_functions = non_daemon_functions @ daemon_functions -- 1.6.2.5
Richard W.M. Jones
2009-Aug-05 09:11 UTC
[Libguestfs] [PATCH] New commands to list devices by UUID and label
On Mon, Aug 03, 2009 at 05:06:54PM +0100, Richard W.M. Jones wrote:> This isn't very well tested at the moment because my main testing > machine is broken. So just for comment, only to be applied with > caution.[...]> + /* In the second pass we look at all devices in /dev and /dev/mapper > + * and try to find matching device major/minor numbers for them in > + * the hash. > + */Actually this algorithm is wrong, although the code is still worth reviewing. It should check /dev/mapper first, and remove any hash entries which match as it goes along. Otherwise it'll find /dev/dm-X devices, and we don't want that to happen ... Rich. -- Richard Jones, Emerging Technologies, Red Hat http://et.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html
Jim Meyering
2009-Aug-05 09:57 UTC
[Libguestfs] [PATCH] New commands to list devices by UUID and label
Richard W.M. Jones wrote:> This isn't very well tested at the moment because my main testing > machine is broken. So just for comment, only to be applied with > caution. > > This patch has two new commands which make it much easier to look up > filesystems by their UUID or label. They are: > > list-devices-by-uuid > list-devices-by-label > > Each returns a hashtable (or whatever the equivalent structure is in > your favourite programming language). > > The keys of the hash are UUID or label. > > The values are the filesystem device, eg. /dev/sda1 or /dev/VG/LVLooks fine on principle. (haven't tried it)> From: Richard W.M. Jones <rjones at redhat.com> > Date: Mon, 3 Aug 2009 11:44:51 +0100 > Subject: [PATCH] New commands: list-disks-by-uuid and list-disks-by-label > > These commands returned a hash from UUID/label -> device, > making it simpler to locate partitions which have UUIDs > or labels. > --- > daemon/Makefile.am | 7 +- > daemon/deviceby.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++++ > po/POTFILES.in | 1 + > src/MAX_PROC_NR | 2 +- > src/generator.ml | 16 +++ > 5 files changed, 318 insertions(+), 3 deletions(-) > create mode 100644 daemon/deviceby.c > > diff --git a/daemon/Makefile.am b/daemon/Makefile.am...> + $(srcdir)/../.gnulib/lib/hash.h \ > + $(srcdir)/../.gnulib/lib/hash.cDon't include these here. This can be handled cleanly by running gnulib-tool separately for (and integrating it into) this subdir. I'm writing details in a separate message.> -guestfsd_CFLAGS = -Wall > +guestfsd_CFLAGS = -Wall -I$(srcdir)/../.gnulib/lib > diff --git a/daemon/deviceby.c b/daemon/deviceby.c > new file mode 100644 > index 0000000..2006442 > --- /dev/null > +++ b/daemon/deviceby.c > @@ -0,0 +1,295 @@ > +/* libguestfs - the guestfsd daemon > + * Copyright (C) 2009 Red Hat Inc. > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + */ > + > +#include <config.h> > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <ctype.h> > +#include <unistd.h> > +#include <fcntl.h> > +#include <dirent.h> > +#include <sys/stat.h> > +#include <sys/types.h> > + > +#include "daemon.h" > +#include "actions.h" > + > +#include "hash.h" /* Gnulib hash table. */ > + > +/* Hash used in the first pass. */ > +struct h1 { > + dev_t dev; /* device */ > + char *dname; /* d_dname entry (ie. UUID or label) */ > +}; > + > +static size_t > +hasher1 (const void *h1v, size_t table_size) > +{ > + const struct h1 *h1 = h1v; > + > + return (unsigned) h1->dev % table_size; > +} > + > +static bool > +comparator1 (const void *h11v, const void *h12v) > +{ > + const struct h1 *h11 = h11v; > + const struct h1 *h12 = h12v; > + > + return h11->dev == h12->dev; > +} > + > +static void > +freer1 (void *h1v) > +{ > + struct h1 *h1 = h1v; > + > + free (h1->dname); > + free (h1); > +} > + > +/* Note that d->d_name can contain \x<X><X> sequences which > + * we must replace with a character (eg. "\x2f" -> "/") > + * > + * How escaping really works in udev is very unclear, so this > + * code is at best a guess. > + */ > +static int > +getxdigit (char c) > +{ > + switch (c) { > + case '0'...'9': return c - '0'; > + case 'a'...'f': return c - 'a' + 10; > + case 'A'...'F': return c - 'A' + 10; > + default: abort (); > + } > +} > + > +static char * > +unescape (const char *dname) > +{ > + int i, j, len; > + char *r; > + > + len = strlen (dname); > + r = malloc (len + 1); > + if (r == NULL) { > + reply_with_perror ("malloc"); > + return NULL; > + } > + > + for (i = j = 0; i < len; ++i, ++j) { > + if (i < len-4 && /* \xAB */ > + dname[i] == '\\' && dname[i+1] == 'x' && > + isxdigit (dname[i+2]) && isxdigit (dname[i+3])) { > + r[j] = getxdigit (dname[i+2]) * 16 + getxdigit (dname[i+3]); > + i += 3; > + } > + else if (i < len-2 && dname[i] == '\\') { /* \\ etc */ > + r[j] = dname[i+1]; > + i++; > + } > + else > + r[j] = dname[i]; > + } > + > + r[j] = '\0'; > + > + return r; > +} > + > +static char ** > +list (const char *udevpath) > +{ > + DIR *dir = NULL; > + struct dirent *d; > + char path[PATH_MAX]; > + struct stat statbuf; > + Hash_table *hash1 = NULL; > + char **ret = NULL; > + int size = 0, alloc = 0; > + > + /* /dev/disk is populated by udev */ > + udev_settle (); > + > + dir = opendir (udevpath); > + if (!dir) { > + /* If there are no labels/UUIDs then udev sometimes won't > + * even create this directory. In this case let's return > + * an empty hashtable. > + */ > + perror (udevpath); > + if (add_string (&ret, &size, &alloc, NULL) == -1) > + return NULL; > + return ret; > + } > + > + /* First pass, over the udev /dev/disk directory. */ > + hash1 = hash_initialize (128, NULL, hasher1, comparator1, freer1); > + if (hash1 == NULL) { > + reply_with_perror ("hash_initialize"); > + goto error; > + } > + > + while ((d = readdir (dir)) != NULL) { > + snprintf (path, sizeof path, "%s/%s", udevpath, d->d_name); > + if (stat (path, &statbuf) == -1) { /* Stat the target of the link. */ > + perror (path); > + continue; > + } > + /* Ignore ".", "..", and any other non-block devices. */ > + if (!S_ISBLK (statbuf.st_mode)) > + continue; > + > + /* Now add a hash entry rdev -> d_name. We'll look up the > + * device name in the second pass. > + */ > + struct h1 *h1 = malloc (sizeof *h1); > + if (h1 == NULL) { > + reply_with_perror ("malloc"); > + goto error; > + } > + h1->dev = statbuf.st_rdev; > + h1->dname = unescape (d->d_name); > + if (!h1->dname) { > + free (h1); > + goto error; > + } > + if (hash_insert (hash1, h1) == NULL) { > + reply_with_perror ("hash_insert"); > + free (h1); > + goto error; > + } > + } > + > + if (closedir (dir) == -1) { > + reply_with_perror (udevpath); > + dir = NULL; > + goto error; > + } > + > + /* In the second pass we look at all devices in /dev and /dev/mapper > + * and try to find matching device major/minor numbers for them in > + * the hash. > + */ > + dir = opendir ("/dev"); > + if (!dir) { > + reply_with_perror ("/dev"); > + goto error; > + } > + > + while ((d = readdir (dir)) != NULL) { > + snprintf (path, sizeof path, "/dev/%s", d->d_name); > + if (stat (path, &statbuf) == -1) { > + perror (path); > + continue; > + } > + > + /* st_rdev is in the hash? If so, it's an entry in the result. */ > + struct h1 h1, *r; > + h1.dev = statbuf.st_rdev; > + r = hash_lookup (hash1, &h1); > + if (r) { > + /* Key: UUID or label */ > + if (add_string (&ret, &size, &alloc, r->dname) == -1) > + goto error; > + /* Value: /dev/... */ > + if (add_string (&ret, &size, &alloc, path) == -1) > + goto error; > + } > + } > + > + if (closedir (dir) == -1) { > + reply_with_perror (udevpath); > + free_strings (ret); > + dir = NULL; > + goto error; > + } > +There's enough repetition of "/dev/mapper" here that I'd factor it out: const char *dev_mapper = "/dev/mapper";> + dir = opendir ("/dev/mapper"); > + if (!dir) { > + reply_with_perror ("/dev/mapper"); > + goto error; > + } > +If you care about detecting/reporting readdir failure, you can set errno=0 before each call, and then test afterwards. errno != 0 means there was an error, e.g., EIO.> + while ((d = readdir (dir)) != NULL) { > + snprintf (path, sizeof path, "/dev/mapper/%s", d->d_name); > + if (stat (path, &statbuf) == -1) { > + perror (path);