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