mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
This command run over the source: perl -pi.bak -e 's/(20[01][0-9])-2018/$1-2019/g' `git ls-files`
544 lines
12 KiB
C
544 lines
12 KiB
C
/* libguestfs
|
|
* Copyright (C) 2009-2019 Red Hat Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <error.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <sys/utsname.h>
|
|
|
|
#include <pcre.h>
|
|
|
|
/* Warn about deprecated libguestfs functions, but only in this file,
|
|
* not in 'tests.c' (because we want to test deprecated functions).
|
|
*/
|
|
#define GUESTFS_WARN_DEPRECATED 1
|
|
|
|
#include "guestfs.h"
|
|
#include "guestfs-utils.h"
|
|
#include "structs-cleanups.h"
|
|
|
|
#include "tests.h"
|
|
|
|
static int is_cross_appliance;
|
|
|
|
int
|
|
init_none (guestfs_h *g)
|
|
{
|
|
/* XXX At some point in the distant past, InitNone and InitEmpty
|
|
* became folded together as the same thing. Really we should make
|
|
* InitNone do nothing at all, but the tests may need to be checked
|
|
* to make sure this is OK.
|
|
*/
|
|
return init_empty (g);
|
|
}
|
|
|
|
int
|
|
init_empty (guestfs_h *g)
|
|
{
|
|
if (guestfs_blockdev_setrw (g, "/dev/sda") == -1)
|
|
return -1;
|
|
|
|
if (guestfs_umount_all (g) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_lvm_remove_all (g) == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
init_partition (guestfs_h *g)
|
|
{
|
|
if (init_empty (g) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_part_disk (g, "/dev/sda", "mbr") == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
init_gpt (guestfs_h *g)
|
|
{
|
|
if (init_empty (g) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_part_disk (g, "/dev/sda", "gpt") == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
init_basic_fs (guestfs_h *g)
|
|
{
|
|
if (init_partition (g) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1)
|
|
return -1;
|
|
|
|
if (guestfs_mount (g, "/dev/sda1", "/") == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
init_basic_fs_on_lvm (guestfs_h *g)
|
|
{
|
|
const char *pvs[] = { "/dev/sda1", NULL };
|
|
|
|
if (init_partition (g) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_pvcreate (g, "/dev/sda1") == -1)
|
|
return -1;
|
|
|
|
if (guestfs_vgcreate (g, "VG", (char **) pvs) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_lvcreate (g, "LV", "VG", 8) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_mkfs (g, "ext2", "/dev/VG/LV") == -1)
|
|
return -1;
|
|
|
|
if (guestfs_mount (g, "/dev/VG/LV", "/") == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
init_iso_fs (guestfs_h *g)
|
|
{
|
|
if (init_empty (g) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_mount_ro (g, "/dev/sdd", "/") == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
init_scratch_fs (guestfs_h *g)
|
|
{
|
|
if (init_empty (g) == -1)
|
|
return -1;
|
|
|
|
if (guestfs_mount (g, "/dev/sdb1", "/") == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
print_strings (char *const *argv)
|
|
{
|
|
size_t argc;
|
|
|
|
for (argc = 0; argv[argc] != NULL; ++argc)
|
|
printf ("\t%s\n", argv[argc]);
|
|
}
|
|
|
|
static int compare_lists (char **, char **, int (*) (const char *, const char *));
|
|
|
|
/* Compare 'ret' to the string list that follows. */
|
|
int
|
|
is_string_list (char **ret, size_t n, ...)
|
|
{
|
|
CLEANUP_FREE /* sic */ char **expected = malloc ((n+1) * sizeof (char *));
|
|
size_t i;
|
|
va_list args;
|
|
|
|
va_start (args, n);
|
|
for (i = 0; i < n; ++i)
|
|
expected[i] = va_arg (args, char *);
|
|
expected[n] = NULL;
|
|
va_end (args);
|
|
return compare_lists (ret, expected, strcmp);
|
|
}
|
|
|
|
/* Compare 'ret' to the device list that follows. */
|
|
int
|
|
is_device_list (char **ret, size_t n, ...)
|
|
{
|
|
CLEANUP_FREE /* sic */ char **expected = malloc ((n+1) * sizeof (char *));
|
|
size_t i;
|
|
va_list args;
|
|
|
|
va_start (args, n);
|
|
for (i = 0; i < n; ++i)
|
|
expected[i] = va_arg (args, char *);
|
|
expected[n] = NULL;
|
|
va_end (args);
|
|
return compare_lists (ret, expected, compare_devices);
|
|
}
|
|
|
|
static int
|
|
compare_lists (char **ret, char **expected,
|
|
int (*compare) (const char *, const char *))
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; ret[i] != NULL; ++i) {
|
|
if (!expected[i]) {
|
|
fprintf (stderr, "test failed: returned list is too long\n");
|
|
goto fail;
|
|
}
|
|
if (compare (ret[i], expected[i]) != 0) {
|
|
fprintf (stderr, "test failed: elements differ at position %zu\n", i);
|
|
goto fail;
|
|
}
|
|
}
|
|
if (expected[i]) {
|
|
fprintf (stderr, "test failed: returned list is too short\n");
|
|
goto fail;
|
|
}
|
|
|
|
return 1; /* test expecting true for OK */
|
|
|
|
fail:
|
|
fprintf (stderr, "returned list was:\n");
|
|
print_strings (ret);
|
|
fprintf (stderr, "expected list was:\n");
|
|
print_strings (expected);
|
|
return 0; /* test expecting false for failure */
|
|
}
|
|
|
|
/* Compare two device names, ignoring hd/sd/ubd/vd */
|
|
int
|
|
compare_devices (const char *a, const char *b)
|
|
{
|
|
size_t alen, blen;
|
|
|
|
/* Skip /dev/ prefix if present. */
|
|
if (STRPREFIX (a, "/dev/"))
|
|
a += 5;
|
|
if (STRPREFIX (b, "/dev/"))
|
|
b += 5;
|
|
|
|
/* Skip sd/hd/ubd/vd. */
|
|
alen = strcspn (a, "d");
|
|
blen = strcspn (b, "d");
|
|
assert (alen > 0 && alen <= 2);
|
|
assert (blen > 0 && blen <= 2);
|
|
a += alen + 1;
|
|
b += blen + 1;
|
|
|
|
return strcmp (a, b);
|
|
}
|
|
|
|
/* Compare returned buffer with expected buffer. Note the buffers have
|
|
* a length and may contain ASCII NUL characters.
|
|
*/
|
|
int
|
|
compare_buffers (const char *b1, size_t s1, const char *b2, size_t s2)
|
|
{
|
|
if (s1 != s2)
|
|
return s1 - s2;
|
|
return memcmp (b1, b2, s1);
|
|
}
|
|
|
|
/* Get md5sum of the named file. */
|
|
static void
|
|
md5sum (const char *filename, char *result)
|
|
{
|
|
char cmd[256];
|
|
snprintf (cmd, sizeof cmd, "md5sum %s", filename);
|
|
FILE *pp = popen (cmd, "r");
|
|
if (pp == NULL)
|
|
error (EXIT_FAILURE, errno, "popen: %s", cmd);
|
|
if (fread (result, 1, 32, pp) != 32)
|
|
error (EXIT_FAILURE, errno, "md5sum: fread");
|
|
if (pclose (pp) != 0)
|
|
error (EXIT_FAILURE, errno, "pclose");
|
|
result[32] = '\0';
|
|
}
|
|
|
|
/* Compare MD5 has to expected hash of a file. */
|
|
int
|
|
check_file_md5 (const char *ret, const char *filename)
|
|
{
|
|
char expected[33];
|
|
|
|
md5sum (filename, expected);
|
|
if (STRNEQ (ret, expected)) {
|
|
fprintf (stderr, "test failed: MD5 returned (%s) does not match MD5 of file %s (%s)\n",
|
|
ret, filename, expected);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Return the value for a key in a hashtable.
|
|
* Note: the return value is part of the hash and should not be freed.
|
|
*/
|
|
const char *
|
|
get_key (char **hash, const char *key)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; hash[i] != NULL; i += 2) {
|
|
if (STREQ (hash[i], key))
|
|
return hash[i+1];
|
|
}
|
|
|
|
return NULL; /* key not found */
|
|
}
|
|
|
|
/* Compare hash key's value to expected value. */
|
|
int
|
|
check_hash (char **ret, const char *key, const char *expected)
|
|
{
|
|
const char *value = get_key (ret, key);
|
|
|
|
if (value == NULL) {
|
|
fprintf (stderr, "test failed: hash key %s not found\n", key);
|
|
return -1;
|
|
}
|
|
|
|
if (STRNEQ (value, expected)) {
|
|
fprintf (stderr, "test failed: hash key %s = \"%s\" is not expected value \"%s\"\n",
|
|
key, value, expected);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Match string with a PCRE regular expression. */
|
|
int
|
|
match_re (const char *str, const char *pattern)
|
|
{
|
|
const char *err;
|
|
int offset;
|
|
pcre *re;
|
|
const size_t len = strlen (str);
|
|
int vec[30], r;
|
|
|
|
re = pcre_compile (pattern, 0, &err, &offset, NULL);
|
|
if (re == NULL)
|
|
error (EXIT_FAILURE, 0,
|
|
"cannot compile regular expression '%s': %s", pattern, err);
|
|
r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
|
|
pcre_free (re);
|
|
|
|
return r != PCRE_ERROR_NOMATCH;
|
|
}
|
|
|
|
/* Used for FileIn parameters in tests. If the path starts with
|
|
* "$srcdir" then replace that with the contents of the $srcdir
|
|
* environment variable (this is set by automake and run time). The
|
|
* caller must free the returned string.
|
|
*/
|
|
char *
|
|
substitute_srcdir (const char *path)
|
|
{
|
|
char *ret;
|
|
|
|
if (STRPREFIX (path, "$srcdir")) {
|
|
const char *srcdir;
|
|
|
|
srcdir = getenv ("srcdir");
|
|
if (!srcdir)
|
|
error (EXIT_FAILURE, 0,
|
|
"environment variable $srcdir is not defined.\n"
|
|
"Normally it is defined by automake. If you are running the\n"
|
|
"tests directly, set $srcdir to point to the source tests/c-api\n"
|
|
"directory.");
|
|
|
|
if (asprintf (&ret, "%s%s", srcdir, path + 7) == -1)
|
|
error (EXIT_FAILURE, errno, "asprintf");
|
|
}
|
|
else {
|
|
ret = strdup (path);
|
|
if (!ret)
|
|
error (EXIT_FAILURE, errno, "strdup");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
using_cross_appliance (void)
|
|
{
|
|
return is_cross_appliance;
|
|
}
|
|
|
|
static void
|
|
next_test (guestfs_h *g, size_t test_num, const char *test_name)
|
|
{
|
|
if (guestfs_get_verbose (g))
|
|
printf ("-------------------------------------------------------------------------------\n");
|
|
printf ("%3zu/%3zu %s\n", test_num, nr_tests, test_name);
|
|
}
|
|
|
|
void
|
|
skipped (const char *test_name, const char *fs, ...)
|
|
{
|
|
va_list args;
|
|
CLEANUP_FREE char *reason = NULL;
|
|
int len;
|
|
|
|
va_start (args, fs);
|
|
len = vasprintf (&reason, fs, args);
|
|
va_end (args);
|
|
assert (len >= 0);
|
|
|
|
printf (" %s skipped (reason: %s)\n",
|
|
test_name, reason);
|
|
}
|
|
|
|
/* Create the handle, with attached disks. */
|
|
static guestfs_h *
|
|
create_handle (void)
|
|
{
|
|
guestfs_h *g;
|
|
|
|
g = guestfs_create ();
|
|
if (g == NULL) {
|
|
printf ("FAIL: guestfs_create\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
if (guestfs_add_drive_scratch (g, INT64_C(2)*1024*1024*1024, -1) == -1) {
|
|
printf ("FAIL: guestfs_add_drive_scratch\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
if (guestfs_add_drive_scratch (g, INT64_C(2)*1024*1024*1024, -1) == -1) {
|
|
printf ("FAIL: guestfs_add_drive_scratch\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
if (guestfs_add_drive_scratch (g, INT64_C(10)*1024*1024, -1) == -1) {
|
|
printf ("FAIL: guestfs_add_drive_scratch\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
if (guestfs_add_drive_ro (g, "../../test-data/test.iso") == -1) {
|
|
printf ("FAIL: guestfs_add_drive_ro ../../test-data/test.iso\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/* Set a timeout in case qemu hangs during launch (RHBZ#505329). */
|
|
alarm (600);
|
|
|
|
if (guestfs_launch (g) == -1) {
|
|
printf ("FAIL: guestfs_launch\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/* Cancel previous alarm. */
|
|
alarm (0);
|
|
|
|
/* Create ext2 filesystem on /dev/sdb1 partition. */
|
|
if (guestfs_part_disk (g, "/dev/sdb", "mbr") == -1) {
|
|
printf ("FAIL: guestfs_part_disk\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
if (guestfs_mkfs (g, "ext2", "/dev/sdb1") == -1) {
|
|
printf ("FAIL: guestfs_mkfs (/dev/sdb1)\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
return g;
|
|
}
|
|
|
|
static int
|
|
check_cross_appliance (guestfs_h *g)
|
|
{
|
|
struct utsname host;
|
|
CLEANUP_FREE_UTSNAME struct guestfs_utsname *appliance = NULL;
|
|
int r;
|
|
struct guestfs_utsname host_utsname;
|
|
|
|
r = uname (&host);
|
|
if (r == -1)
|
|
error (EXIT_FAILURE, errno, "uname");
|
|
|
|
appliance = guestfs_utsname (g);
|
|
if (appliance == NULL)
|
|
exit (EXIT_FAILURE);
|
|
|
|
host_utsname.uts_sysname = host.sysname;
|
|
host_utsname.uts_release = host.release;
|
|
host_utsname.uts_version = host.version;
|
|
host_utsname.uts_machine = host.machine;
|
|
|
|
return guestfs_compare_utsname (appliance, &host_utsname);
|
|
}
|
|
|
|
static size_t
|
|
perform_tests (guestfs_h *g)
|
|
{
|
|
size_t test_num;
|
|
size_t nr_failed = 0;
|
|
struct test *t;
|
|
|
|
for (test_num = 0; test_num < nr_tests; ++test_num) {
|
|
t = &tests[test_num];
|
|
next_test (g, test_num, t->name);
|
|
if (t->test_fn (g) == -1) {
|
|
printf ("FAIL: %s\n", t->name);
|
|
nr_failed++;
|
|
}
|
|
}
|
|
|
|
return nr_failed;
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
size_t nr_failed;
|
|
guestfs_h *g;
|
|
|
|
setbuf (stdout, NULL);
|
|
|
|
no_test_warnings ();
|
|
|
|
g = create_handle ();
|
|
is_cross_appliance = check_cross_appliance (g);
|
|
|
|
nr_failed = perform_tests (g);
|
|
|
|
guestfs_close (g);
|
|
|
|
if (nr_failed > 0) {
|
|
printf ("***** %zu / %zu tests FAILED *****\n", nr_failed, nr_tests);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
exit (EXIT_SUCCESS);
|
|
}
|