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