fish: handle some out-of-memory conditions
[libguestfs.git] / fish / destpaths.c
1 /* guestfish - the filesystem interactive shell
2  * Copyright (C) 2009 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #define _GNU_SOURCE             // for strndup, asprintf
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stddef.h>
26 #include <string.h>
27
28 #ifdef HAVE_LIBREADLINE
29 #include <readline/readline.h>
30 #endif
31
32 #include <guestfs.h>
33
34 #include "fish.h"
35
36 // From gnulib's xalloc.h:
37 /* Return 1 if an array of N objects, each of size S, cannot exist due
38    to size arithmetic overflow.  S must be positive and N must be
39    nonnegative.  This is a macro, not an inline function, so that it
40    works correctly even when SIZE_MAX < N.
41
42    By gnulib convention, SIZE_MAX represents overflow in size
43    calculations, so the conservative dividend to use here is
44    SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value.
45    However, malloc (SIZE_MAX) fails on all known hosts where
46    sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for
47    exactly-SIZE_MAX allocations on such hosts; this avoids a test and
48    branch when S is known to be 1.  */
49 # define xalloc_oversized(n, s) \
50     ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n))
51
52 /* Readline completion for paths on the guest filesystem, also for
53  * devices and LVM names.
54  */
55
56 int complete_dest_paths = 0; /* SEE NOTE */
57
58 /* NOTE: This is currently disabled by default (with no way to
59  * enable it).  That's because it's not particularly natural.
60  *
61  * Also there is a quite serious performance problem.  When listing
62  * even moderately long directories, this takes many seconds.  The
63  * reason is because it calls guestfs_is_dir on each directory
64  * entry, thus lots of round trips to the server.  We could have
65  * a "readdir and stat each entry" call to ease this.
66  */
67
68 char *
69 complete_dest_paths_generator (const char *text, int state)
70 {
71 #ifdef HAVE_LIBREADLINE
72
73   static size_t len, index;
74   static char **words = NULL;
75   static size_t nr_words = 0;
76   char *word;
77   guestfs_error_handler_cb old_error_cb;
78   void *old_error_cb_data;
79
80   /* Temporarily replace the error handler so that messages don't
81    * get printed to stderr while we are issuing commands.
82    */
83 #define SAVE_ERROR_CB                                                   \
84   old_error_cb = guestfs_get_error_handler (g, &old_error_cb_data);     \
85   guestfs_set_error_handler (g, NULL, NULL);
86
87   /* Restore error handler. */
88 #define RESTORE_ERROR_CB                                                \
89   guestfs_set_error_handler (g, old_error_cb, old_error_cb_data);
90
91   if (!state) {
92     char **strs;
93
94     len = strlen (text);
95     index = 0;
96
97     if (words) {
98       size_t i;
99       /* NB. 'words' array is NOT NULL-terminated. */
100       for (i = 0; i < nr_words; ++i)
101         free (words[i]);
102       free (words);
103     }
104
105     words = NULL;
106     nr_words = 0;
107
108     SAVE_ERROR_CB
109
110 /* Silently do nothing if an allocation fails */
111 #define APPEND_STRS_AND_FREE                                            \
112   do {                                                                  \
113     if (strs) {                                                         \
114       size_t n = count_strings (strs);                                  \
115       if ( ! xalloc_oversized (nr_words + n, sizeof (char *))) {        \
116         char *w = realloc (words, sizeof (char *) * (nr_words + n));    \
117         if (w == NULL) {                                                \
118           free (words);                                                 \
119           words = NULL;                                                 \
120           nr_words = 0;                                                 \
121         } else {                                                        \
122           size_t i;                                                     \
123           for (i = 0; i < n; ++i)                                       \
124             words[nr_words++] = strs[i];                                \
125         }                                                               \
126         free (strs);                                                    \
127       }                                                                 \
128     }                                                                   \
129   } while (0)
130
131     /* Is it a device? */
132     if (len < 5 || strncmp (text, "/dev/", 5) == 0) {
133       /* Get a list of everything that can possibly begin with /dev/ */
134       strs = guestfs_list_devices (g);
135       APPEND_STRS_AND_FREE;
136
137       strs = guestfs_list_partitions (g);
138       APPEND_STRS_AND_FREE;
139
140       strs = guestfs_lvs (g);
141       APPEND_STRS_AND_FREE;
142     }
143
144     if (len < 1 || text[0] == '/') {
145       /* If we've got a partial path already, we need to list everything
146        * in that directory, otherwise list everything in /
147        */
148       char *p, *dir;
149
150       p = strrchr (text, '/');
151       dir = p && p > text ? strndup (text, p - text) : strdup ("/");
152       if (dir) {
153         strs = guestfs_ls (g, dir);
154
155         /* Prepend directory to names. */
156         if (strs) {
157           size_t i;
158           for (i = 0; strs[i]; ++i) {
159             int err;
160             if (strcmp (dir, "/") == 0)
161               err = asprintf (&p, "/%s", strs[i]);
162             else
163               err = asprintf (&p, "%s/%s", dir, strs[i]);
164             if (0 <= err) {
165               free (strs[i]);
166               strs[i] = p;
167             }
168           }
169         }
170
171         free (dir);
172         APPEND_STRS_AND_FREE;
173       }
174     }
175
176     /* else ...  In theory we could complete other things here such as VG
177      * names.  At the moment we don't do that.
178      */
179
180     RESTORE_ERROR_CB
181   }
182
183   /* This inhibits ordinary (local filename) completion. */
184   rl_attempted_completion_over = 1;
185
186   /* Complete the string. */
187   while (index < nr_words) {
188     word = words[index];
189     index++;
190     if (strncasecmp (word, text, len) == 0) {
191       /* Is it a directory? */
192       if (strncmp (word, "/dev/", 5) != 0) {
193         SAVE_ERROR_CB
194         if (guestfs_is_dir (g, word) > 0)
195           rl_completion_append_character = '/';
196         RESTORE_ERROR_CB
197       }
198
199       return strdup (word);
200     }
201   }
202
203 #endif /* HAVE_LIBREADLINE */
204
205   return NULL;
206 }