Richard W.M. Jones
2012-May-02 15:39 UTC
[Libguestfs] [PATCH 0/4] fish: Allow the glob command to expand device patterns (RHBZ#635971).
This patch set fixes a two year old bug in guestfish, namely that the 'glob' command does not expand /dev/* patterns. https://bugzilla.redhat.com/show_bug.cgi?id=635971 Rich.
Richard W.M. Jones
2012-May-02 15:39 UTC
[Libguestfs] [PATCH 1/4] fish: Clean up glob code and make it return error if malloc fails.
From: "Richard W.M. Jones" <rjones at redhat.com> This commit tidies up the code for the 'glob' command. It also makes the command return an error if malloc fails (previously it would just print a message but not return any error indication). --- fish/glob.c | 93 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/fish/glob.c b/fish/glob.c index b3cfa57..88e8927 100644 --- a/fish/glob.c +++ b/fish/glob.c @@ -29,6 +29,9 @@ /* A bit tricky because in the case where there are multiple * paths we have to perform a Cartesian product. */ + +static char **expand_pathname (guestfs_h *g, const char *path); +static char **single_element_list (const char *element); static void glob_issue (char *cmd, size_t argc, char ***globs, size_t *posn, size_t *count, int *r); int @@ -69,50 +72,19 @@ run_glob (const char *cmd, size_t argc, char *argv[]) /* Only if it begins with '/' can it possibly be a globbable path. */ if (argv[i][0] == '/') { - pp = guestfs_glob_expand (g, argv[i]); - if (pp == NULL) { /* real error in glob_expand */ - fprintf (stderr, _("glob: guestfs_glob_expand call failed: %s\n"), - argv[i]); - goto error0; - } - - /* If there were no matches, then we add a single element list - * containing just the original argv[i] string. - */ - if (pp[0] == NULL) { - char **pp2; - - pp2 = realloc (pp, sizeof (char *) * 2); - if (pp2 == NULL) { - perror ("realloc"); - free (pp); - goto error0; - } - pp = pp2; - - pp[0] = strdup (argv[i]); - if (pp[0] == NULL) { - perror ("strdup"); - free (pp); - goto error0; - } - pp[1] = NULL; + pp = expand_pathname (g, argv[i]); + if (pp == NULL) { + r = -1; + goto error; } } /* Doesn't begin with '/' */ else { - pp = malloc (sizeof (char *) * 2); + pp = single_element_list (argv[i]); if (pp == NULL) { - perror ("malloc"); - goto error0; - } - pp[0] = strdup (argv[i]); - if (pp[0] == NULL) { - perror ("strdup"); - free (pp); - goto error0; + r = -1; + goto error; } - pp[1] = NULL; } globs[i] = pp; @@ -123,13 +95,56 @@ run_glob (const char *cmd, size_t argc, char *argv[]) glob_issue (argv[0], argc, globs, posn, count, &r); /* Free resources. */ - error0: + error: for (i = 1; i < argc; ++i) if (globs[i]) free_strings (globs[i]); return r; } +static char ** +expand_pathname (guestfs_h *g, const char *path) +{ + char **pp; + + pp = guestfs_glob_expand (g, path); + if (pp == NULL) { /* real error in glob_expand */ + fprintf (stderr, _("glob: guestfs_glob_expand call failed: %s\n"), path); + return NULL; + } + + if (pp[0] != NULL) + return pp; /* Return the non-empty list of matches. */ + + /* If there were no matches, then we add a single element list + * containing just the original string. + */ + free (pp); + return single_element_list (path); +} + +/* Return a single element list containing 'element'. */ +static char ** +single_element_list (const char *element) +{ + char **pp; + + pp = malloc (sizeof (char *) * 2); + if (pp == NULL) { + perror ("malloc"); + return NULL; + } + pp[0] = strdup (element); + if (pp[0] == NULL) { + perror ("strdup"); + free (pp); + return NULL; + } + pp[1] = NULL; + + return pp; +} + static void glob_issue (char *cmd, size_t argc, char ***globs, size_t *posn, size_t *count, -- 1.7.10
Richard W.M. Jones
2012-May-02 15:40 UTC
[Libguestfs] [PATCH 2/4] fish: Move 'feature_available' function to global.
From: "Richard W.M. Jones" <rjones at redhat.com> This is just code motion. --- fish/edit.c | 20 -------------------- fish/fish.c | 19 +++++++++++++++++++ fish/fish.h | 1 + 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/fish/edit.c b/fish/edit.c index a411180..b26a2dd 100644 --- a/fish/edit.c +++ b/fish/edit.c @@ -33,7 +33,6 @@ static char *generate_random_name (const char *filename); static int copy_attributes (const char *src, const char *dest); -static int feature_available (guestfs_h *g, const char *feature); /* guestfish edit command, suggested by J?n Ondrej, implemented by RWMJ */ @@ -242,22 +241,3 @@ copy_attributes (const char *src, const char *dest) return 0; } - -static int -feature_available (guestfs_h *g, const char *feature) -{ - /* If there's an error we should ignore it, so to do that we have to - * temporarily replace the error handler with a null one. - */ - guestfs_error_handler_cb old_error_cb; - void *old_error_data; - old_error_cb = guestfs_get_error_handler (g, &old_error_data); - guestfs_set_error_handler (g, NULL, NULL); - - const char *groups[] = { feature, NULL }; - int r = guestfs_available (g, (char * const *) groups); - - guestfs_set_error_handler (g, old_error_cb, old_error_data); - - return r == 0 ? 1 : 0; -} diff --git a/fish/fish.c b/fish/fish.c index 63fb30f..80b3364 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -1790,3 +1790,22 @@ progress_callback (guestfs_h *g, void *data, progress_bar_set (bar, position, total); } + +int +feature_available (guestfs_h *g, const char *feature) +{ + /* If there's an error we should ignore it, so to do that we have to + * temporarily replace the error handler with a null one. + */ + guestfs_error_handler_cb old_error_cb; + void *old_error_data; + old_error_cb = guestfs_get_error_handler (g, &old_error_data); + guestfs_set_error_handler (g, NULL, NULL); + + const char *groups[] = { feature, NULL }; + int r = guestfs_available (g, (char * const *) groups); + + guestfs_set_error_handler (g, old_error_cb, old_error_data); + + return r == 0 ? 1 : 0; +} diff --git a/fish/fish.h b/fish/fish.h index 69a7640..cc4065a 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -76,6 +76,7 @@ extern void free_file_in (char *s); extern char *file_out (const char *arg); extern void extended_help_message (void); extern void progress_callback (guestfs_h *g, void *data, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len); +extern int feature_available (guestfs_h *g, const char *feature); /* in cmds.c (auto-generated) */ extern void list_commands (void); -- 1.7.10
Richard W.M. Jones
2012-May-02 15:40 UTC
[Libguestfs] [PATCH 3/4] fish: glob command now expands /dev/ patterns (RHBZ#635971).
From: "Richard W.M. Jones" <rjones at redhat.com> For example:><fs> glob echo /dev/*/dev/vda /dev/vda1 /dev/vda2 /dev/vda3><fs> glob echo /dev/v*/*/dev/vg_f16x64/lv_root /dev/vg_f16x64/lv_swap --- fish/glob.c | 143 +++++++++++++++++++++++++++++++++++++++- generator/generator_actions.ml | 6 +- 2 files changed, 146 insertions(+), 3 deletions(-) diff --git a/fish/glob.c b/fish/glob.c index 88e8927..075f69a 100644 --- a/fish/glob.c +++ b/fish/glob.c @@ -22,6 +22,8 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <errno.h> +#include <fnmatch.h> #include <libintl.h> #include "fish.h" @@ -31,6 +33,9 @@ */ static char **expand_pathname (guestfs_h *g, const char *path); +static char **expand_devicename (guestfs_h *g, const char *device); +static int add_strings_matching (char **pp, const char *glob, char ***ret, size_t *size_r); +static int add_string (const char *str, char ***ret, size_t *size_r); static char **single_element_list (const char *element); static void glob_issue (char *cmd, size_t argc, char ***globs, size_t *posn, size_t *count, int *r); @@ -70,8 +75,18 @@ run_glob (const char *cmd, size_t argc, char *argv[]) for (i = 1; i < argc; ++i) { char **pp; - /* Only if it begins with '/' can it possibly be a globbable path. */ - if (argv[i][0] == '/') { + /* If it begins with "/dev/" then treat it as a globbable device + * name. + */ + if (STRPREFIX (argv[i], "/dev/")) { + pp = expand_devicename (g, argv[i]); + if (pp == NULL) { + r = -1; + goto error; + } + } + /* If it begins with "/" it might be a globbable pathname. */ + else if (argv[i][0] == '/') { pp = expand_pathname (g, argv[i]); if (pp == NULL) { r = -1; @@ -123,6 +138,130 @@ expand_pathname (guestfs_h *g, const char *path) return single_element_list (path); } +/* Glob-expand device patterns, such as "/dev/sd*" (RHBZ#635971). + * + * There is no 'guestfs_glob_expand_device' function because the + * equivalent can be implemented using functions like + * 'guestfs_list_devices'. + * + * It's not immediately clear what it means to expand a pattern like + * "/dev/sd*". Should that include device name translation? Should + * the result include partitions as well as devices? + * + * Should "/dev/" + "*" return every possible device and filesystem? + * How about VGs? LVs? + * + * To solve this what we do is build up a list of every device, + * partition, etc., then glob against that list. + * + * Notes for future work (XXX): + * - This doesn't handle device name translation. It wouldn't be + * too hard to add. + * - Could have an API function for returning all device-like things. + */ +static char ** +expand_devicename (guestfs_h *g, const char *device) +{ + char **pp = NULL; + char **ret = NULL; + size_t size = 0; + + pp = guestfs_list_devices (g); + if (pp == NULL) goto error; + if (add_strings_matching (pp, device, &ret, &size) == -1) goto error; + free_strings (pp); + + pp = guestfs_list_partitions (g); + if (pp == NULL) goto error; + if (add_strings_matching (pp, device, &ret, &size) == -1) goto error; + free_strings (pp); + + pp = guestfs_list_md_devices (g); + if (pp == NULL) goto error; + if (add_strings_matching (pp, device, &ret, &size) == -1) goto error; + free_strings (pp); + + if (feature_available (g, "lvm2")) { + pp = guestfs_lvs (g); + if (pp == NULL) goto error; + if (add_strings_matching (pp, device, &ret, &size) == -1) goto error; + free_strings (pp); + pp = NULL; + } + + /* None matched? Add the original glob pattern. */ + if (ret == NULL) + ret = single_element_list (device); + return ret; + + error: + if (pp) + free_strings (pp); + if (ret) + free_strings (ret); + + return NULL; +} + +/* Using fnmatch, find strings in the list 'pp' which match pattern + * 'glob'. Add strings which match to the 'ret' array. '*size_r' is + * the current size of the 'ret' array, which is updated with the new + * size. + */ +static int +add_strings_matching (char **pp, const char *glob, + char ***ret, size_t *size_r) +{ + size_t i; + int r; + + for (i = 0; pp[i] != NULL; ++i) { + errno = 0; + r = fnmatch (glob, pp[i], FNM_PATHNAME); + if (r == 0) { /* matches - add it */ + if (add_string (pp[i], ret, size_r) == -1) + return -1; + } + else if (r != FNM_NOMATCH) { /* error */ + /* I checked the glibc impl and it returns random negative + * numbers for errors. It doesn't always set errno. Do our + * best here to record the error state. + */ + fprintf (stderr, "glob: fnmatch: error (r = %d, errno = %d)\n", + r, errno); + return -1; + } + } + + return 0; +} + +static int +add_string (const char *str, char ***ret, size_t *size_r) +{ + char **new_ret = *ret; + size_t size = *size_r; + + new_ret = realloc (new_ret, (size + 2) * (sizeof (char *))); + if (!new_ret) { + perror ("realloc"); + return -1; + } + *ret = new_ret; + + new_ret[size] = strdup (str); + if (new_ret[size] == NULL) { + perror ("strdup"); + return -1; + } + + size++; + new_ret[size] = NULL; + *size_r = size; + + return 0; +} + /* Return a single element list containing 'element'. */ static char ** single_element_list (const char *element) diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml index ace46cb..6948dab 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -3700,7 +3700,11 @@ If no paths match, then this returns an empty list It is just a wrapper around the C L<glob(3)> function with flags C<GLOB_MARK|GLOB_BRACE>. -See that manual page for more details."); +See that manual page for more details. + +Notice that there is no equivalent command for expanding a device +name (eg. C</dev/sd*>). Use C<guestfs_list_devices>, +C<guestfs_list_partitions> etc functions instead."); ("scrub_device", (RErr, [Device "device"], []), 114, [Optional "scrub"], [InitNone, Always, TestRun ( (* use /dev/sdc because it's smaller *) -- 1.7.10
Richard W.M. Jones
2012-May-02 15:40 UTC
[Libguestfs] [PATCH 4/4] fish: Add a regression test for the 'glob' command.
From: "Richard W.M. Jones" <rjones at redhat.com> --- fish/Makefile.am | 2 + fish/test-glob.sh | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100755 fish/test-glob.sh diff --git a/fish/Makefile.am b/fish/Makefile.am index d809ba1..6b52820 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -248,6 +248,7 @@ TESTS += \ test-copy.sh \ test-edit.sh \ test-find0.sh \ + test-glob.sh \ test-mount-local.sh \ test-read_file.sh \ test-remote.sh \ @@ -265,6 +266,7 @@ EXTRA_DIST += \ test-escapes.sh \ test-events.sh \ test-find0.sh \ + test-glob.sh \ test-mount-local.sh \ test-read_file.sh \ test-remote.sh \ diff --git a/fish/test-glob.sh b/fish/test-glob.sh new file mode 100755 index 0000000..0d54dbe --- /dev/null +++ b/fish/test-glob.sh @@ -0,0 +1,105 @@ +#!/bin/bash - +# libguestfs +# Copyright (C) 2012 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Test guestfish glob command. + +set -e + +rm -f test.img test.out + +./guestfish > test.out <<EOF + +sparse test.img 1G +run + +pvcreate /dev/sda +# Because glob doesn't do device name translation, we cannot test +# matching on /dev/sd* paths, only on LVs. So choose a volume group +# name that cannot possibly be a device name. +vgcreate abc /dev/sda +lvcreate lv1 abc 64 +lvcreate lv2 abc 64 +lvcreate lv3 abc 64 + +glob mkfs ext2 /dev/abc/* +mount /dev/abc/lv1 / + +mkdir /foo +touch /abc +touch /foo/bar1 +touch /foo/bar2 + +# Regular file globbing. +echo files +glob echo /f* +glob echo /foo/* +glob echo /foo/not* +glob echo /foo/b??1 +glob echo /abc + +# Device globbing. +echo devices +glob echo /dev/a* +glob echo /dev/a*/* +glob echo /dev/a*/not* +glob echo /dev/a*/lv? +glob echo /dev/a*/lv +glob echo /dev/a*/*3 +glob echo /dev/a*/* /dev/a* +glob echo /dev/a*/* /dev/a*/* + +echo end +EOF + +if [ "$(cat test.out)" != "files +/foo/ +/foo/bar1 +/foo/bar2 +/foo/not* +/foo/bar1 +/abc +devices +/dev/a* +/dev/abc/lv1 +/dev/abc/lv2 +/dev/abc/lv3 +/dev/a*/not* +/dev/abc/lv1 +/dev/abc/lv2 +/dev/abc/lv3 +/dev/a*/lv +/dev/abc/lv3 +/dev/abc/lv1 /dev/a* +/dev/abc/lv2 /dev/a* +/dev/abc/lv3 /dev/a* +/dev/abc/lv1 /dev/abc/lv1 +/dev/abc/lv1 /dev/abc/lv2 +/dev/abc/lv1 /dev/abc/lv3 +/dev/abc/lv2 /dev/abc/lv1 +/dev/abc/lv2 /dev/abc/lv2 +/dev/abc/lv2 /dev/abc/lv3 +/dev/abc/lv3 /dev/abc/lv1 +/dev/abc/lv3 /dev/abc/lv2 +/dev/abc/lv3 /dev/abc/lv3 +end" ]; then + echo "$0: error: unexpected output from glob command" + cat test.out + exit 1 +fi + +rm -f test.img test.out -- 1.7.10