X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=fish%2Fdestpaths.c;h=403fe7f5b4353336b3aca524bdaff9e4767c793c;hp=6cddafa2faf1549a7178f9f4eb1a4cfa9541a48a;hb=53c3b9d2b03fa5cb0ac7e86a5a51f2a18a2b91c1;hpb=e395d7db8fd3fe3d1b121258fe2f401d6b3e64c8 diff --git a/fish/destpaths.c b/fish/destpaths.c index 6cddafa..403fe7f 100644 --- a/fish/destpaths.c +++ b/fish/destpaths.c @@ -1,5 +1,5 @@ /* guestfish - the filesystem interactive shell - * Copyright (C) 2009 Red Hat Inc. + * 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 @@ -18,10 +18,9 @@ #include -#define _GNU_SOURCE // for strndup, asprintf - #include #include +#include #include #ifdef HAVE_LIBREADLINE @@ -32,31 +31,64 @@ #include "fish.h" +#ifdef HAVE_LIBREADLINE +// From gnulib's xalloc.h: +/* Return 1 if an array of N objects, each of size S, cannot exist due + to size arithmetic overflow. S must be positive and N must be + nonnegative. This is a macro, not an inline function, so that it + works correctly even when SIZE_MAX < N. + + By gnulib convention, SIZE_MAX represents overflow in size + calculations, so the conservative dividend to use here is + SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value. + However, malloc (SIZE_MAX) fails on all known hosts where + sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for + exactly-SIZE_MAX allocations on such hosts; this avoids a test and + branch when S is known to be 1. */ +# define xalloc_oversized(n, s) \ + ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n)) +#endif + /* Readline completion for paths on the guest filesystem, also for * devices and LVM names. */ -int complete_dest_paths = 0; /* SEE NOTE */ +int complete_dest_paths = 1; -/* NOTE: This is currently disabled by default (with no way to - * enable it). That's because it's not particularly natural. - * - * Also there is a quite serious performance problem. When listing - * even moderately long directories, this takes many seconds. The - * reason is because it calls guestfs_is_dir on each directory - * entry, thus lots of round trips to the server. We could have - * a "readdir and stat each entry" call to ease this. - */ +struct word { + char *name; + int is_dir; +}; + +#ifdef HAVE_LIBREADLINE +static void +free_words (struct word *words, size_t nr_words) +{ + size_t i; + + /* NB. 'words' array is NOT NULL-terminated. */ + for (i = 0; i < nr_words; ++i) + free (words[i].name); + free (words); +} + +static int +compare_words (const void *vp1, const void *vp2) +{ + const struct word *w1 = (const struct word *) vp1; + const struct word *w2 = (const struct word *) vp2; + return strcmp (w1->name, w2->name); +} +#endif char * complete_dest_paths_generator (const char *text, int state) { #ifdef HAVE_LIBREADLINE - static int len, index; - static char **words = NULL; - static int nr_words = 0; - char *word; + static size_t len, index; + static struct word *words = NULL; + static size_t nr_words = 0; guestfs_error_handler_cb old_error_cb; void *old_error_cb_data; @@ -73,43 +105,56 @@ complete_dest_paths_generator (const char *text, int state) if (!state) { char **strs; - int i, n; len = strlen (text); index = 0; - if (words) { - /* NB. 'words' array is NOT NULL-terminated. */ - for (i = 0; i < nr_words; ++i) - free (words[i]); - free (words); - } + if (words) free_words (words, nr_words); words = NULL; nr_words = 0; SAVE_ERROR_CB +/* Silently do nothing if an allocation fails */ #define APPEND_STRS_AND_FREE \ + do { \ if (strs) { \ - n = count_strings (strs); \ - words = realloc (words, sizeof (char *) * (nr_words + n)); \ - for (i = 0; i < n; ++i) \ - words[nr_words++] = strs[i]; \ + size_t i; \ + size_t n = count_strings (strs); \ + \ + if ( n > 0 && ! xalloc_oversized (nr_words + n, sizeof (struct word))) { \ + struct word *w; \ + w = realloc (words, sizeof (struct word) * (nr_words + n)); \ + \ + if (w == NULL) { \ + free_words (words, nr_words); \ + words = NULL; \ + nr_words = 0; \ + } else { \ + words = w; \ + for (i = 0; i < n; ++i) { \ + words[nr_words].name = strs[i]; \ + words[nr_words].is_dir = 0; \ + nr_words++; \ + } \ + } \ + } \ free (strs); \ - } + } \ + } while (0) /* Is it a device? */ - if (len < 5 || strncmp (text, "/dev/", 5) == 0) { + if (len < 5 || STREQLEN (text, "/dev/", 5)) { /* Get a list of everything that can possibly begin with /dev/ */ strs = guestfs_list_devices (g); - APPEND_STRS_AND_FREE + APPEND_STRS_AND_FREE; strs = guestfs_list_partitions (g); - APPEND_STRS_AND_FREE + APPEND_STRS_AND_FREE; strs = guestfs_lvs (g); - APPEND_STRS_AND_FREE + APPEND_STRS_AND_FREE; } if (len < 1 || text[0] == '/') { @@ -117,27 +162,52 @@ complete_dest_paths_generator (const char *text, int state) * in that directory, otherwise list everything in / */ char *p, *dir; + struct guestfs_dirent_list *dirents; p = strrchr (text, '/'); dir = p && p > text ? strndup (text, p - text) : strdup ("/"); - - strs = guestfs_ls (g, dir); - - /* Prepend directory to names. */ - if (strs) { - for (i = 0; strs[i]; ++i) { - p = NULL; - if (strcmp (dir, "/") == 0) - asprintf (&p, "/%s", strs[i]); - else - asprintf (&p, "%s/%s", dir, strs[i]); - free (strs[i]); - strs[i] = p; - } + if (dir) { + dirents = guestfs_readdir (g, dir); + + /* Prepend directory to names before adding them to the list + * of words. + */ + if (dirents) { + size_t i; + + for (i = 0; i < dirents->len; ++i) { + int err; + + if (STRNEQ (dirents->val[i].name, ".") && + STRNEQ (dirents->val[i].name, "..")) { + if (STREQ (dir, "/")) + err = asprintf (&p, "/%s", dirents->val[i].name); + else + err = asprintf (&p, "%s/%s", dir, dirents->val[i].name); + if (err >= 0) { + if (!xalloc_oversized (nr_words+1, sizeof (struct word))) { + struct word *w; + + w = realloc (words, sizeof (struct word) * (nr_words+1)); + if (w == NULL) { + free_words (words, nr_words); + words = NULL; + nr_words = 0; + } + else { + words = w; + words[nr_words].name = p; + words[nr_words].is_dir = dirents->val[i].ftyp == 'd'; + nr_words++; + } + } + } + } + } + + guestfs_free_dirent_list (dirents); + } } - - free (dir); - APPEND_STRS_AND_FREE } /* else ... In theory we could complete other things here such as VG @@ -150,20 +220,21 @@ complete_dest_paths_generator (const char *text, int state) /* This inhibits ordinary (local filename) completion. */ rl_attempted_completion_over = 1; + /* Sort the words so the list is stable over multiple calls. */ + qsort (words, nr_words, sizeof (struct word), compare_words); + /* Complete the string. */ while (index < nr_words) { - word = words[index]; + struct word *word; + + word = &words[index]; index++; - if (strncasecmp (word, text, len) == 0) { - /* Is it a directory? */ - if (strncmp (word, "/dev/", 5) != 0) { - SAVE_ERROR_CB - if (guestfs_is_dir (g, word) > 0) - rl_completion_append_character = '/'; - RESTORE_ERROR_CB - } - return strdup (word); + if (STRCASEEQLEN (word->name, text, len)) { + if (word->is_dir) + rl_completion_append_character = '/'; + + return strdup (word->name); } }