Richard W.M. Jones
2009-Aug-06 14:09 UTC
[Libguestfs] [PATCH] New commands to list devices by UUID and label (updated patch)
A new version of this patch, previously posted here: https://www.redhat.com/archives/libguestfs/2009-August/thread.html#00031 I've made some of the changes that Jim suggested, but I didn't fix the error handling of readdir because that loop always reads from a tmpfs. It didn't seem like there would realistically be any read errors(?) These commands are compromised in their usefulness by a long-standing LVM bug (https://bugzilla.redhat.com/show_bug.cgi?id=465691), which means they won't list anything for LVM devices on Fedora. They should work on non-Fedora-derived platforms however. Rich. -- Richard Jones, Emerging Technologies, Red Hat http://et.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From cc1857aff4d4e3942b8f28c5a6e37c5b8fbb2793 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Mon, 3 Aug 2009 17:06:54 +0100 Subject: [PATCH 1/2] New commands to list devices by UUID and label. 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 --- daemon/Makefile.am | 1 + daemon/deviceby.c | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES.in | 1 + src/MAX_PROC_NR | 2 +- src/generator.ml | 26 +++++ 5 files changed, 330 insertions(+), 1 deletions(-) create mode 100644 daemon/deviceby.c diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 43cc752..090338d 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -30,6 +30,7 @@ guestfsd_SOURCES = \ cpmv.c \ daemon.h \ debug.c \ + deviceby.c \ devsparts.c \ df.c \ dir.c \ diff --git a/daemon/deviceby.c b/daemon/deviceby.c new file mode 100644 index 0000000..a5d280f --- /dev/null +++ b/daemon/deviceby.c @@ -0,0 +1,301 @@ +/* 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/mapper and /dev + * and try to find matching device major/minor numbers for them in + * the hash. + */ + const char *devmapperdir = "/dev/mapper"; + dir = opendir (devmapperdir); + if (!dir) { + reply_with_perror (devmapperdir); + goto error; + } + + while ((d = readdir (dir)) != NULL) { + snprintf (path, sizeof path, "%s/%s", devmapperdir, 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; + + hash_delete (hash1, &h1); + } + } + + if (closedir (dir) == -1) { + reply_with_perror (udevpath); + free_strings (ret); + dir = NULL; + goto error; + } + + const char *devdir = "/dev"; + dir = opendir (devdir); + if (!dir) { + reply_with_perror (devdir); + goto error; + } + + while ((d = readdir (dir)) != NULL) { + snprintf (path, sizeof path, "%s/%s", devdir, 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; + + hash_delete (hash1, &h1); + } + } + + 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 382cd3a..8aade33 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 7ac4ac8..c6689d4 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -3397,6 +3397,32 @@ 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. + +Note that the implementation of this relies on udev and +C</dev/disk/by-*> files being created. On (at least) Fedora, +there is a long-standing bug which means these files are +not always created correctly for LVM devices."); + + ("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. + +Note that the implementation of this relies on udev and +C</dev/disk/by-*> files being created. On (at least) Fedora, +there is a long-standing bug which means these files are +not always created correctly for LVM devices."); + ] let all_functions = non_daemon_functions @ daemon_functions -- 1.6.2.5
Matthew Booth
2009-Aug-06 15:15 UTC
[Libguestfs] [PATCH] New commands to list devices by UUID and label (updated patch)
On 06/08/09 15:09, Richard W.M. Jones wrote:> A new version of this patch, previously posted here: > > https://www.redhat.com/archives/libguestfs/2009-August/thread.html#00031 > > I've made some of the changes that Jim suggested, but I didn't fix the > error handling of readdir because that loop always reads from a tmpfs. > It didn't seem like there would realistically be any read errors(?)It always reads from a tmpfs today. But in a few months somebody's going to change it so that it doesn't for some unrelated really good reason, and then it'll be a bug :) Matt -- Matthew Booth, RHCA, RHCSS Red Hat Engineering, Virtualisation Team M: +44 (0)7977 267231 GPG ID: D33C3490 GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490