tests: Rename caution -> tests/qemu.
[libguestfs.git] / cat / virt-cat.c
1 /* virt-cat
2  * Copyright (C) 2010-2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <inttypes.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <locale.h>
28 #include <assert.h>
29 #include <libintl.h>
30
31 #include "progname.h"
32 #include "c-ctype.h"
33
34 #include "guestfs.h"
35 #include "options.h"
36
37 /* Currently open libguestfs handle. */
38 guestfs_h *g;
39
40 int read_only = 1;
41 int live = 0;
42 int verbose = 0;
43 int keys_from_stdin = 0;
44 int echo_keys = 0;
45 const char *libvirt_uri = NULL;
46 int inspector = 1;
47
48 static int is_windows (guestfs_h *g, const char *root);
49 static char *windows_path (guestfs_h *g, const char *root, const char *filename);
50
51 static inline char *
52 bad_cast (char const *s)
53 {
54   return (char *) s;
55 }
56
57 static void __attribute__((noreturn))
58 usage (int status)
59 {
60   if (status != EXIT_SUCCESS)
61     fprintf (stderr, _("Try `%s --help' for more information.\n"),
62              program_name);
63   else {
64     fprintf (stdout,
65            _("%s: display files in a virtual machine\n"
66              "Copyright (C) 2010 Red Hat Inc.\n"
67              "Usage:\n"
68              "  %s [--options] -d domname file [file ...]\n"
69              "  %s [--options] -a disk.img [-a disk.img ...] file [file ...]\n"
70              "Options:\n"
71              "  -a|--add image       Add image\n"
72              "  -c|--connect uri     Specify libvirt URI for -d option\n"
73              "  -d|--domain guest    Add disks from libvirt guest\n"
74              "  --echo-keys          Don't turn off echo for passphrases\n"
75              "  --format[=raw|..]    Force disk format for -a option\n"
76              "  --help               Display brief help\n"
77              "  --keys-from-stdin    Read passphrases from stdin\n"
78              "  -v|--verbose         Verbose messages\n"
79              "  -V|--version         Display version and exit\n"
80              "  -x                   Trace libguestfs API calls\n"
81              "For more information, see the manpage %s(1).\n"),
82              program_name, program_name, program_name,
83              program_name);
84   }
85   exit (status);
86 }
87
88 int
89 main (int argc, char *argv[])
90 {
91   /* Set global program name that is not polluted with libtool artifacts.  */
92   set_program_name (argv[0]);
93
94   setlocale (LC_ALL, "");
95   bindtextdomain (PACKAGE, LOCALEBASEDIR);
96   textdomain (PACKAGE);
97
98   enum { HELP_OPTION = CHAR_MAX + 1 };
99
100   static const char *options = "a:c:d:vVx";
101   static const struct option long_options[] = {
102     { "add", 1, 0, 'a' },
103     { "connect", 1, 0, 'c' },
104     { "domain", 1, 0, 'd' },
105     { "echo-keys", 0, 0, 0 },
106     { "format", 2, 0, 0 },
107     { "help", 0, 0, HELP_OPTION },
108     { "keys-from-stdin", 0, 0, 0 },
109     { "verbose", 0, 0, 'v' },
110     { "version", 0, 0, 'V' },
111     { 0, 0, 0, 0 }
112   };
113   struct drv *drvs = NULL;
114   struct drv *drv;
115   const char *format = NULL;
116   int c;
117   int option_index;
118
119   g = guestfs_create ();
120   if (g == NULL) {
121     fprintf (stderr, _("guestfs_create: failed to create handle\n"));
122     exit (EXIT_FAILURE);
123   }
124
125   argv[0] = bad_cast (program_name);
126
127   for (;;) {
128     c = getopt_long (argc, argv, options, long_options, &option_index);
129     if (c == -1) break;
130
131     switch (c) {
132     case 0:                     /* options which are long only */
133       if (STREQ (long_options[option_index].name, "keys-from-stdin")) {
134         keys_from_stdin = 1;
135       } else if (STREQ (long_options[option_index].name, "echo-keys")) {
136         echo_keys = 1;
137       } else if (STREQ (long_options[option_index].name, "format")) {
138         if (!optarg || STREQ (optarg, ""))
139           format = NULL;
140         else
141           format = optarg;
142       } else {
143         fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
144                  program_name, long_options[option_index].name, option_index);
145         exit (EXIT_FAILURE);
146       }
147       break;
148
149     case 'a':
150       OPTION_a;
151       break;
152
153     case 'c':
154       OPTION_c;
155       break;
156
157     case 'd':
158       OPTION_d;
159       break;
160
161     case 'h':
162       usage (EXIT_SUCCESS);
163
164     case 'v':
165       OPTION_v;
166       break;
167
168     case 'V':
169       OPTION_V;
170       break;
171
172     case 'x':
173       OPTION_x;
174       break;
175
176     case HELP_OPTION:
177       usage (EXIT_SUCCESS);
178
179     default:
180       usage (EXIT_FAILURE);
181     }
182   }
183
184   /* Old-style syntax?  There were no -a or -d options in the old
185    * virt-cat which is how we detect this.
186    */
187   if (drvs == NULL) {
188     /* argc - 1 because last parameter is the single filename. */
189     while (optind < argc - 1) {
190       if (strchr (argv[optind], '/') ||
191           access (argv[optind], F_OK) == 0) { /* simulate -a option */
192         drv = malloc (sizeof (struct drv));
193         if (!drv) {
194           perror ("malloc");
195           exit (EXIT_FAILURE);
196         }
197         drv->type = drv_a;
198         drv->a.filename = argv[optind];
199         drv->a.format = NULL;
200         drv->next = drvs;
201         drvs = drv;
202       } else {                  /* simulate -d option */
203         drv = malloc (sizeof (struct drv));
204         if (!drv) {
205           perror ("malloc");
206           exit (EXIT_FAILURE);
207         }
208         drv->type = drv_d;
209         drv->d.guest = argv[optind];
210         drv->next = drvs;
211         drvs = drv;
212       }
213
214       optind++;
215     }
216   }
217
218   /* These are really constants, but they have to be variables for the
219    * options parsing code.  Assert here that they have known-good
220    * values.
221    */
222   assert (read_only == 1);
223   assert (inspector == 1);
224   assert (live == 0);
225
226   /* User must specify at least one filename on the command line. */
227   if (optind >= argc || argc - optind < 1)
228     usage (EXIT_FAILURE);
229
230   /* User must have specified some drives. */
231   if (drvs == NULL)
232     usage (EXIT_FAILURE);
233
234   /* Add drives, inspect and mount.  Note that inspector is always true,
235    * and there is no -m option.
236    */
237   add_drives (drvs, 'a');
238
239   if (guestfs_launch (g) == -1)
240     exit (EXIT_FAILURE);
241
242   inspect_mount ();
243
244   /* Free up data structures, no longer needed after this point. */
245   free_drives (drvs);
246
247   unsigned errors = 0;
248   int windows;
249   char *root, **roots;
250
251   /* Get root mountpoint.  See: fish/inspect.c:inspect_mount */
252   roots = guestfs_inspect_get_roots (g);
253   assert (roots);
254   assert (roots[0] != NULL);
255   assert (roots[1] == NULL);
256   root = roots[0];
257   free (roots);
258
259   /* Windows?  Special handling is required. */
260   windows = is_windows (g, root);
261
262   for (; optind < argc; optind++) {
263     char *filename_to_free = NULL;
264     const char *filename = argv[optind];
265
266     if (windows) {
267       filename = filename_to_free = windows_path (g, root, filename);
268       if (filename == NULL) {
269         errors++;
270         continue;
271       }
272     }
273
274     if (guestfs_download (g, filename, "/dev/stdout") == -1)
275       errors++;
276
277     free (filename_to_free);
278   }
279
280   free (root);
281
282   guestfs_close (g);
283
284   exit (errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
285 }
286
287 static int
288 is_windows (guestfs_h *g, const char *root)
289 {
290   char *type;
291   int w;
292
293   type = guestfs_inspect_get_type (g, root);
294   if (!type)
295     return 0;
296
297   w = STREQ (type, "windows");
298   free (type);
299   return w;
300 }
301
302 static void mount_drive_letter_ro (char drive_letter, const char *root);
303
304 static char *
305 windows_path (guestfs_h *g, const char *root, const char *path)
306 {
307   char *ret;
308   size_t i;
309
310   /* If there is a drive letter, rewrite the path. */
311   if (c_isalpha (path[0]) && path[1] == ':') {
312     char drive_letter = c_tolower (path[0]);
313     /* This returns the newly allocated string. */
314     mount_drive_letter_ro (drive_letter, root);
315     ret = strdup (path + 2);
316     if (ret == NULL) {
317       perror ("strdup");
318       exit (EXIT_FAILURE);
319     }
320   }
321   else if (!*path) {
322     ret = strdup ("/");
323     if (ret == NULL) {
324       perror ("strdup");
325       exit (EXIT_FAILURE);
326     }
327   }
328   else {
329     ret = strdup (path);
330     if (ret == NULL) {
331       perror ("strdup");
332       exit (EXIT_FAILURE);
333     }
334   }
335
336   /* Blindly convert any backslashes into forward slashes.  Is this good? */
337   for (i = 0; i < strlen (ret); ++i)
338     if (ret[i] == '\\')
339       ret[i] = '/';
340
341   /* If this fails, we want to return NULL. */
342   char *t = guestfs_case_sensitive_path (g, ret);
343   free (ret);
344   ret = t;
345
346   return ret;
347 }
348
349 static void
350 mount_drive_letter_ro (char drive_letter, const char *root)
351 {
352   char **drives;
353   char *device;
354   size_t i;
355
356   /* Resolve the drive letter using the drive mappings table. */
357   drives = guestfs_inspect_get_drive_mappings (g, root);
358   if (drives == NULL || drives[0] == NULL) {
359     fprintf (stderr, _("%s: to use Windows drive letters, this must be a Windows guest\n"),
360              program_name);
361     exit (EXIT_FAILURE);
362   }
363
364   device = NULL;
365   for (i = 0; drives[i] != NULL; i += 2) {
366     if (c_tolower (drives[i][0]) == drive_letter && drives[i][1] == '\0') {
367       device = drives[i+1];
368       break;
369     }
370   }
371
372   if (device == NULL) {
373     fprintf (stderr, _("%s: drive '%c:' not found.\n"),
374              program_name, drive_letter);
375     exit (EXIT_FAILURE);
376   }
377
378   /* Unmount current disk and remount device. */
379   if (guestfs_umount_all (g) == -1)
380     exit (EXIT_FAILURE);
381
382   if (guestfs_mount_ro (g, device, "/") == -1)
383     exit (EXIT_FAILURE);
384
385   for (i = 0; drives[i] != NULL; ++i)
386     free (drives[i]);
387   free (drives);
388   /* Don't need to free (device) because that string was in the
389    * drives array.
390    */
391 }