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