todo: Add notes on inspecting ISO images.
[libguestfs.git] / test-tool / test-tool.c
1 /* libguestfs-test-tool
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 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <inttypes.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <getopt.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/wait.h>
32 #include <locale.h>
33 #include <limits.h>
34
35 #include <guestfs.h>
36
37 #ifdef HAVE_GETTEXT
38 #include "gettext.h"
39 #define _(str) dgettext(PACKAGE, (str))
40 #define N_(str) dgettext(PACKAGE, (str))
41 #else
42 #define _(str) str
43 #define N_(str) str
44 #endif
45
46 #if !ENABLE_NLS
47 #undef textdomain
48 #define textdomain(Domainname) /* empty */
49 #undef bindtextdomain
50 #define bindtextdomain(Domainname, Dirname) /* empty */
51 #endif
52
53 #define STREQ(a,b) (strcmp((a),(b)) == 0)
54 #define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
55 #define STRNEQ(a,b) (strcmp((a),(b)) != 0)
56 #define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
57 #define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
58 #define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
59 #define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
60 #define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
61 #define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
62
63 #ifndef P_tmpdir
64 #define P_tmpdir "/tmp"
65 #endif
66
67 #define DEFAULT_TIMEOUT 120
68
69 static const char *helper = DEFAULT_HELPER;
70 static int timeout = DEFAULT_TIMEOUT;
71 static char tmpf[] = P_tmpdir "/libguestfs-test-tool-sda-XXXXXX";
72 static char isof[] = P_tmpdir "/libguestfs-test-tool-iso-XXXXXX";
73 static guestfs_h *g;
74
75 static void preruncheck (void);
76 static void make_files (void);
77 static void set_qemu (const char *path, int use_wrapper);
78
79 static void
80 usage (void)
81 {
82   printf (_("libguestfs-test-tool: interactive test tool\n"
83             "Copyright (C) 2009 Red Hat Inc.\n"
84             "Usage:\n"
85             "  libguestfs-test-tool [--options]\n"
86             "Options:\n"
87             "  --help         Display usage\n"
88             "  --helper libguestfs-test-tool-helper\n"
89             "                 Helper program (default: %s)\n"
90             "  --qemudir dir  Specify QEMU source directory\n"
91             "  --qemu qemu    Specify QEMU binary\n"
92             "  --timeout n\n"
93             "  -t n           Set launch timeout (default: %d seconds)\n"
94             ),
95           DEFAULT_HELPER, DEFAULT_TIMEOUT);
96 }
97
98 int
99 main (int argc, char *argv[])
100 {
101   setlocale (LC_ALL, "");
102   bindtextdomain (PACKAGE, LOCALEBASEDIR);
103   textdomain (PACKAGE);
104
105   static const char *options = "t:?";
106   static const struct option long_options[] = {
107     { "help", 0, 0, '?' },
108     { "helper", 1, 0, 0 },
109     { "qemu", 1, 0, 0 },
110     { "qemudir", 1, 0, 0 },
111     { "timeout", 1, 0, 't' },
112     { 0, 0, 0, 0 }
113   };
114   int c;
115   int option_index;
116   extern char **environ;
117   int i;
118   struct guestfs_version *vers;
119   char *sfdisk_lines[] = { ",", NULL };
120   char *str;
121   /* XXX This is wrong if the user renames the helper. */
122   char *helper_args[] = { "/iso/libguestfs-test-tool-helper", NULL };
123
124   for (;;) {
125     c = getopt_long (argc, argv, options, long_options, &option_index);
126     if (c == -1) break;
127
128     switch (c) {
129     case 0:                     /* options which are long only */
130       if (STREQ (long_options[option_index].name, "helper"))
131         helper = optarg;
132       else if (STREQ (long_options[option_index].name, "qemu"))
133         set_qemu (optarg, 0);
134       else if (STREQ (long_options[option_index].name, "qemudir"))
135         set_qemu (optarg, 1);
136       else {
137         fprintf (stderr,
138                  _("libguestfs-test-tool: unknown long option: %s (%d)\n"),
139                  long_options[option_index].name, option_index);
140         exit (EXIT_FAILURE);
141       }
142       break;
143
144     case 't':
145       if (sscanf (optarg, "%d", &timeout) != 1 || timeout < 0) {
146         fprintf (stderr,
147                  _("libguestfs-test-tool: invalid timeout: %s\n"),
148                  optarg);
149         exit (EXIT_FAILURE);
150       }
151       break;
152
153     case '?':
154       usage ();
155       exit (EXIT_SUCCESS);
156
157     default:
158       fprintf (stderr,
159                _("libguestfs-test-tool: unexpected command line option 0x%x\n"),
160                c);
161       exit (EXIT_FAILURE);
162     }
163   }
164
165   preruncheck ();
166   make_files ();
167
168   printf ("===== Test starts here =====\n");
169
170   /* Must set LIBGUESTFS_DEBUG=1 */
171   setenv ("LIBGUESTFS_DEBUG", "1", 1);
172
173   /* Print out any environment variables which may relate to this test. */
174   for (i = 0; environ[i] != NULL; ++i)
175     if (STREQLEN (environ[i], "LIBGUESTFS_", 11))
176       printf ("%s\n", environ[i]);
177
178   /* Create the handle and configure it. */
179   g = guestfs_create ();
180   if (g == NULL) {
181     fprintf (stderr,
182              _("libguestfs-test-tool: failed to create libguestfs handle\n"));
183     exit (EXIT_FAILURE);
184   }
185   if (guestfs_add_drive_opts (g, tmpf,
186                               GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
187                               -1) == -1) {
188     fprintf (stderr,
189              _("libguestfs-test-tool: failed to add drive '%s'\n"),
190              tmpf);
191     exit (EXIT_FAILURE);
192   }
193   if (guestfs_add_drive_opts (g, isof,
194                               GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
195                               GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
196                               -1) == -1) {
197     fprintf (stderr,
198              _("libguestfs-test-tool: failed to add drive '%s'\n"),
199              isof);
200     exit (EXIT_FAILURE);
201   }
202
203   /* Print any version info etc. */
204   vers = guestfs_version (g);
205   if (vers == NULL) {
206     fprintf (stderr, _("libguestfs-test-tool: guestfs_version failed\n"));
207     exit (EXIT_FAILURE);
208   }
209   printf ("library version: %"PRIi64".%"PRIi64".%"PRIi64"%s\n",
210           vers->major, vers->minor, vers->release, vers->extra);
211   guestfs_free_version (vers);
212
213   printf ("guestfs_get_append: %s\n", guestfs_get_append (g) ? : "(null)");
214   printf ("guestfs_get_autosync: %d\n", guestfs_get_autosync (g));
215   printf ("guestfs_get_memsize: %d\n", guestfs_get_memsize (g));
216   printf ("guestfs_get_path: %s\n", guestfs_get_path (g));
217   printf ("guestfs_get_qemu: %s\n", guestfs_get_qemu (g));
218   printf ("guestfs_get_verbose: %d\n", guestfs_get_verbose (g));
219
220   /* Launch the guest handle. */
221   printf ("Launching appliance, timeout set to %d seconds.\n", timeout);
222   fflush (stdout);
223
224   alarm (timeout);
225
226   if (guestfs_launch (g) == -1) {
227     fprintf (stderr,
228              _("libguestfs-test-tool: failed to launch appliance\n"));
229     exit (EXIT_FAILURE);
230   }
231
232   alarm (0);
233
234   printf ("Guest launched OK.\n");
235   fflush (stdout);
236
237   /* Create the filesystem and mount everything. */
238   if (guestfs_sfdiskM (g, "/dev/sda", sfdisk_lines) == -1) {
239     fprintf (stderr,
240              _("libguestfs-test-tool: failed to run sfdisk\n"));
241     exit (EXIT_FAILURE);
242   }
243
244   if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1) {
245     fprintf (stderr,
246              _("libguestfs-test-tool: failed to mkfs.ext2\n"));
247     exit (EXIT_FAILURE);
248   }
249
250   if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1) {
251     fprintf (stderr,
252              _("libguestfs-test-tool: failed to mount /dev/sda1 on /\n"));
253     exit (EXIT_FAILURE);
254   }
255
256   if (guestfs_mkdir (g, "/iso") == -1) {
257     fprintf (stderr,
258              _("libguestfs-test-tool: failed to mkdir /iso\n"));
259     exit (EXIT_FAILURE);
260   }
261
262   if (guestfs_mount (g, "/dev/sdb", "/iso") == -1) {
263     fprintf (stderr,
264              _("libguestfs-test-tool: failed to mount /dev/sdb on /iso\n"));
265     exit (EXIT_FAILURE);
266   }
267
268   /* Let's now run some simple tests using the helper program. */
269   str = guestfs_command (g, helper_args);
270   if (str == NULL) {
271     fprintf (stderr,
272              _("libguestfs-test-tool: could not run helper program, or helper failed\n"));
273     exit (EXIT_FAILURE);
274   }
275   free (str);
276
277   printf ("===== TEST FINISHED OK =====\n");
278   exit (EXIT_SUCCESS);
279 }
280
281 static char qemuwrapper[] = P_tmpdir "/libguestfs-test-tool-wrapper-XXXXXX";
282
283 static void
284 cleanup_wrapper (void)
285 {
286   unlink (qemuwrapper);
287 }
288
289 /* Handle the --qemu and --qemudir parameters.  use_wrapper is true
290  * in the --qemudir (source directory) case, where we have to create
291  * a wrapper shell script.
292  */
293 static void
294 set_qemu (const char *path, int use_wrapper)
295 {
296   char buffer[PATH_MAX];
297   struct stat statbuf;
298   int fd;
299   FILE *fp;
300
301   if (getenv ("LIBGUESTFS_QEMU")) {
302     fprintf (stderr,
303     _("LIBGUESTFS_QEMU environment variable is already set, so\n"
304       "--qemu/--qemudir options cannot be used.\n"));
305     exit (EXIT_FAILURE);
306   }
307
308   if (!use_wrapper) {
309     if (access (path, X_OK) == -1) {
310       fprintf (stderr,
311                _("Binary '%s' does not exist or is not executable\n"),
312                path);
313       exit (EXIT_FAILURE);
314     }
315
316     setenv ("LIBGUESTFS_QEMU", path, 1);
317     return;
318   }
319
320   /* This should be a source directory, so check it. */
321   snprintf (buffer, sizeof buffer, "%s/pc-bios", path);
322   if (stat (buffer, &statbuf) == -1 ||
323       !S_ISDIR (statbuf.st_mode)) {
324     fprintf (stderr,
325              _("%s: does not look like a qemu source directory\n"),
326              path);
327     exit (EXIT_FAILURE);
328   }
329
330   /* Make a wrapper script. */
331   fd = mkstemp (qemuwrapper);
332   if (fd == -1) {
333     perror (qemuwrapper);
334     exit (EXIT_FAILURE);
335   }
336
337   fchmod (fd, 0700);
338
339   fp = fdopen (fd, "w");
340   fprintf (fp,
341            "#!/bin/sh -\n"
342            "qemudir='%s'\n"
343            "\"$qemudir\"/",
344            path);
345
346   /* Select the right qemu binary for the wrapper script. */
347 #ifdef __i386__
348   fprintf (fp, "i386-softmmu/qemu");
349 #else
350   fprintf (fp, host_cpu "-softmmu/qemu-system-" host_cpu);
351 #endif
352
353   fprintf (fp, " -L \"$qemudir\"/pc-bios \"$@\"\n");
354
355   fclose (fp);
356
357   setenv ("LIBGUESTFS_QEMU", qemuwrapper, 1);
358   atexit (cleanup_wrapper);
359 }
360
361 /* After getting the command line args, but before running
362  * anything, we check everything is in place to do the tests.
363  */
364 static void
365 preruncheck (void)
366 {
367   int r;
368   FILE *fp;
369   char cmd[256];
370   char buffer[1024];
371
372   if (access (helper, R_OK) == -1) {
373     fprintf (stderr,
374     _("Test tool helper program 'libguestfs-test-tool-helper' is not\n"
375       "available.  Expected to find it in '%s'\n"
376       "\n"
377       "Use the --helper option to specify the location of this program.\n"),
378              helper);
379     exit (EXIT_FAILURE);
380   }
381
382   snprintf (cmd, sizeof cmd, "file '%s'", helper);
383   fp = popen (cmd, "r");
384   if (fp == NULL) {
385     perror (cmd);
386     exit (EXIT_FAILURE);
387   }
388   r = fread (buffer, 1, sizeof buffer - 1, fp);
389   if (r == 0) {
390     fprintf (stderr, _("command failed: %s"), cmd);
391     exit (EXIT_FAILURE);
392   }
393   pclose (fp);
394   buffer[r] = '\0';
395
396   if (strstr (buffer, "statically linked") == NULL) {
397     fprintf (stderr,
398     _("Test tool helper program %s\n"
399       "is not statically linked.  This is a build error when this test tool\n"
400       "was built.\n"),
401              helper);
402     exit (EXIT_FAILURE);
403   }
404 }
405
406 static void
407 cleanup_tmpfiles (void)
408 {
409   unlink (tmpf);
410   unlink (isof);
411 }
412
413 static void
414 make_files (void)
415 {
416   int fd, r;
417   char cmd[256];
418
419   /* Make the ISO which will contain the helper program. */
420   fd = mkstemp (isof);
421   if (fd == -1) {
422     perror (isof);
423     exit (EXIT_FAILURE);
424   }
425   close (fd);
426
427   snprintf (cmd, sizeof cmd, "mkisofs -quiet -rJT -o '%s' '%s'",
428             isof, helper);
429   r = system (cmd);
430   if (r == -1 || WEXITSTATUS(r) != 0) {
431     fprintf (stderr,
432              _("mkisofs command failed: %s\n"), cmd);
433     exit (EXIT_FAILURE);
434   }
435
436   /* Allocate the sparse file for /dev/sda. */
437   fd = mkstemp (tmpf);
438   if (fd == -1) {
439     perror (tmpf);
440     unlink (isof);
441     exit (EXIT_FAILURE);
442   }
443
444   if (lseek (fd, 100 * 1024 * 1024 - 1, SEEK_SET) == -1) {
445     perror ("lseek");
446     close (fd);
447     unlink (tmpf);
448     unlink (isof);
449     exit (EXIT_FAILURE);
450   }
451
452   if (write (fd, "\0", 1) == -1) {
453     perror ("write");
454     close (fd);
455     unlink (tmpf);
456     unlink (isof);
457     exit (EXIT_FAILURE);
458   }
459
460   close (fd);
461
462   atexit (cleanup_tmpfiles);    /* Removes tmpf and isof. */
463 }