Richard W.M. Jones
2016-Jan-21 15:48 UTC
[Libguestfs] [PATCH v3 0/6] [FOR COMMENTS ONLY] Rework inspection.
For background on this change, see: https://rwmj.wordpress.com/2015/12/06/inspection-now-with-added-prolog/ v2 was previously posted here: https://www.redhat.com/archives/libguestfs/2015-December/msg00038.html To test this patch series on a real guest, you can do: $ ./run guestfish -v -x -a /var/tmp/centos-6.img ><fs> run ><fs> debug sh "guestfs-inspection --verbose" v2 -> v3: - Rebase on top of upstream libguestfs. - Allow prologue and epilogue C code. - Minor cleanups. Rich.
Richard W.M. Jones
2016-Jan-21 15:48 UTC
[Libguestfs] [PATCH v3 1/6] daemon: Rename daemon/command.c -> daemon/sh.c.
Simply a file rename, no other change. --- daemon/Makefile.am | 2 +- daemon/command.c | 319 ----------------------------------------------------- daemon/sh.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES | 2 +- 4 files changed, 321 insertions(+), 321 deletions(-) delete mode 100644 daemon/command.c create mode 100644 daemon/sh.c diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 8055235..c1a33e3 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -96,7 +96,6 @@ guestfsd_SOURCES = \ cap.c \ checksum.c \ cmp.c \ - command.c \ compress.c \ copy.c \ cpio.c \ @@ -166,6 +165,7 @@ guestfsd_SOURCES = \ scrub.c \ selinux.c \ sfdisk.c \ + sh.c \ sleep.c \ stat.c \ statvfs.c \ diff --git a/daemon/command.c b/daemon/command.c deleted file mode 100644 index c4efa5b..0000000 --- a/daemon/command.c +++ /dev/null @@ -1,319 +0,0 @@ -/* libguestfs - the guestfsd daemon - * Copyright (C) 2009 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 <stdbool.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <fcntl.h> - -#include "guestfs_protocol.h" -#include "daemon.h" -#include "actions.h" - -#include "ignore-value.h" - -GUESTFSD_EXT_CMD(str_cp, cp); -GUESTFSD_EXT_CMD(str_mount, mount); -GUESTFSD_EXT_CMD(str_umount, umount); - -#ifdef HAVE_ATTRIBUTE_CLEANUP -#define CLEANUP_BIND_STATE __attribute__((cleanup(free_bind_state))) -#define CLEANUP_RESOLVER_STATE __attribute__((cleanup(free_resolver_state))) -#else -#define CLEANUP_BIND_STATE -#define CLEANUP_RESOLVER_STATE -#endif - -struct bind_state { - bool mounted; - char *sysroot_dev; - char *sysroot_dev_pts; - char *sysroot_proc; - char *sysroot_selinux; - char *sysroot_sys; - char *sysroot_sys_fs_selinux; - bool dev_ok, dev_pts_ok, proc_ok, selinux_ok, sys_ok, sys_fs_selinux_ok; -}; - -struct resolver_state { - bool mounted; - char *sysroot_etc_resolv_conf; - char *sysroot_etc_resolv_conf_old; -}; - -/* While running the command, bind-mount /dev, /proc, /sys - * into the chroot. However we must be careful to unmount them - * afterwards because otherwise they would interfere with - * future mount and unmount operations. - * - * We deliberately allow these commands to fail silently, BUT - * if a mount fails, don't unmount the corresponding mount. - */ -static int -bind_mount (struct bind_state *bs) -{ - int r; - - memset (bs, 0, sizeof *bs); - - bs->sysroot_dev = sysroot_path ("/dev"); - bs->sysroot_dev_pts = sysroot_path ("/dev/pts"); - bs->sysroot_proc = sysroot_path ("/proc"); - bs->sysroot_selinux = sysroot_path ("/selinux"); - bs->sysroot_sys = sysroot_path ("/sys"); - bs->sysroot_sys_fs_selinux = sysroot_path ("/sys/fs/selinux"); - - if (bs->sysroot_dev == NULL || bs->sysroot_dev_pts == NULL || - bs->sysroot_proc == NULL || bs->sysroot_selinux == NULL || - bs->sysroot_sys == NULL || bs->sysroot_sys_fs_selinux == NULL) { - reply_with_perror ("malloc"); - free (bs->sysroot_dev); - free (bs->sysroot_dev_pts); - free (bs->sysroot_proc); - free (bs->sysroot_selinux); - free (bs->sysroot_sys); - free (bs->sysroot_sys_fs_selinux); - return -1; - } - - /* Note it is tempting to use --rbind here (to bind submounts). - * However I have not found a reliable way to unmount the same set - * of directories (umount -R does NOT work). - */ - r = command (NULL, NULL, str_mount, "--bind", "/dev", bs->sysroot_dev, NULL); - bs->dev_ok = r != -1; - r = command (NULL, NULL, str_mount, "--bind", "/dev/pts", bs->sysroot_dev_pts, NULL); - bs->dev_pts_ok = r != -1; - r = command (NULL, NULL, str_mount, "--bind", "/proc", bs->sysroot_proc, NULL); - bs->proc_ok = r != -1; - /* Note on the next line we have to bind-mount /sys/fs/selinux (appliance - * kernel) on top of /selinux (where guest is expecting selinux). - */ - r = command (NULL, NULL, str_mount, "--bind", "/sys/fs/selinux", bs->sysroot_selinux, NULL); - bs->selinux_ok = r != -1; - r = command (NULL, NULL, str_mount, "--bind", "/sys", bs->sysroot_sys, NULL); - bs->sys_ok = r != -1; - r = command (NULL, NULL, str_mount, "--bind", "/sys/fs/selinux", bs->sysroot_sys_fs_selinux, NULL); - bs->sys_fs_selinux_ok = r != -1; - - bs->mounted = true; - - return 0; -} - -static inline void -umount_ignore_fail (const char *path) -{ - ignore_value (command (NULL, NULL, str_umount, path, NULL)); -} - -static void -free_bind_state (struct bind_state *bs) -{ - if (bs->mounted) { - if (bs->sys_fs_selinux_ok) umount_ignore_fail (bs->sysroot_sys_fs_selinux); - free (bs->sysroot_sys_fs_selinux); - if (bs->sys_ok) umount_ignore_fail (bs->sysroot_sys); - free (bs->sysroot_sys); - if (bs->selinux_ok) umount_ignore_fail (bs->sysroot_selinux); - free (bs->sysroot_selinux); - if (bs->proc_ok) umount_ignore_fail (bs->sysroot_proc); - free (bs->sysroot_proc); - if (bs->dev_pts_ok) umount_ignore_fail (bs->sysroot_dev_pts); - free (bs->sysroot_dev_pts); - if (bs->dev_ok) umount_ignore_fail (bs->sysroot_dev); - free (bs->sysroot_dev); - bs->mounted = false; - } -} - -/* If the network is enabled, we want <sysroot>/etc/resolv.conf to - * reflect the contents of /etc/resolv.conf so that name resolution - * works. It would be nice to bind-mount the file (single file bind - * mounts are possible). However annoyingly that doesn't work for - * Ubuntu guests where the guest resolv.conf is a dangling symlink, - * and for reasons unknown mount tries to follow the symlink and - * fails (likely a bug). So this is a hack. Note we only invoke - * this if the network is enabled. - */ -static int -set_up_etc_resolv_conf (struct resolver_state *rs) -{ - struct stat statbuf; - - rs->sysroot_etc_resolv_conf_old = NULL; - - rs->sysroot_etc_resolv_conf = sysroot_path ("/etc/resolv.conf"); - - if (!rs->sysroot_etc_resolv_conf) { - reply_with_perror ("malloc"); - goto error; - } - - /* If /etc/resolv.conf exists, rename it to the backup file. Note - * that on Ubuntu it's a dangling symlink. - */ - if (lstat (rs->sysroot_etc_resolv_conf, &statbuf) == 0) { - size_t len = sysroot_len + 32; - char buf[len]; - - /* Make a random name for the backup file. */ - snprintf (buf, len, "%s/etc/XXXXXXXX", sysroot); - if (random_name (buf) == -1) { - reply_with_perror ("random_name"); - goto error; - } - rs->sysroot_etc_resolv_conf_old = strdup (buf); - if (!rs->sysroot_etc_resolv_conf_old) { - reply_with_perror ("strdup"); - goto error; - } - - if (verbose) - fprintf (stderr, "renaming %s to %s\n", rs->sysroot_etc_resolv_conf, - rs->sysroot_etc_resolv_conf_old); - - if (rename (rs->sysroot_etc_resolv_conf, - rs->sysroot_etc_resolv_conf_old) == -1) { - reply_with_perror ("rename: %s to %s", rs->sysroot_etc_resolv_conf, - rs->sysroot_etc_resolv_conf_old); - goto error; - } - } - - /* Now that the guest's <sysroot>/etc/resolv.conf is out the way, we - * can create our own copy of the appliance /etc/resolv.conf. - */ - ignore_value (command (NULL, NULL, str_cp, "/etc/resolv.conf", - rs->sysroot_etc_resolv_conf, NULL)); - - rs->mounted = true; - return 0; - - error: - free (rs->sysroot_etc_resolv_conf); - free (rs->sysroot_etc_resolv_conf_old); - return -1; -} - -static void -free_resolver_state (struct resolver_state *rs) -{ - if (rs->mounted) { - unlink (rs->sysroot_etc_resolv_conf); - - if (rs->sysroot_etc_resolv_conf_old) { - if (verbose) - fprintf (stderr, "renaming %s to %s\n", rs->sysroot_etc_resolv_conf_old, - rs->sysroot_etc_resolv_conf); - - if (rename (rs->sysroot_etc_resolv_conf_old, - rs->sysroot_etc_resolv_conf) == -1) - perror ("error: could not restore /etc/resolv.conf"); - - free (rs->sysroot_etc_resolv_conf_old); - } - - free (rs->sysroot_etc_resolv_conf); - rs->mounted = false; - } -} - -char * -do_command (char *const *argv) -{ - char *out; - CLEANUP_FREE char *err = NULL; - int r, flags; - CLEANUP_BIND_STATE struct bind_state bind_state = { .mounted = false }; - CLEANUP_RESOLVER_STATE struct resolver_state resolver_state - { .mounted = false }; - - /* We need a root filesystem mounted to do this. */ - NEED_ROOT (, return NULL); - - /* Conveniently, argv is already a NULL-terminated argv-style array - * of parameters, so we can pass it straight in to our internal - * commandv. We just have to check the list is non-empty. - */ - if (argv[0] == NULL) { - reply_with_error ("passed an empty list"); - return NULL; - } - - if (bind_mount (&bind_state) == -1) - return NULL; - if (enable_network) { - if (set_up_etc_resolv_conf (&resolver_state) == -1) - return NULL; - } - - flags = COMMAND_FLAG_DO_CHROOT; - - r = commandvf (&out, &err, flags, (const char * const *) argv); - - free_bind_state (&bind_state); - free_resolver_state (&resolver_state); - - if (r == -1) { - reply_with_error ("%s", err); - free (out); - return NULL; - } - - return out; /* Caller frees. */ -} - -char ** -do_command_lines (char *const *argv) -{ - CLEANUP_FREE char *out = NULL; - char **lines; - - out = do_command (argv); - if (out == NULL) - return NULL; - - lines = split_lines (out); - - if (lines == NULL) - return NULL; - - return lines; /* Caller frees. */ -} - -char * -do_sh (const char *cmd) -{ - const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; - - return do_command ((char **) argv); -} - -char ** -do_sh_lines (const char *cmd) -{ - const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; - - return do_command_lines ((char **) argv); -} diff --git a/daemon/sh.c b/daemon/sh.c new file mode 100644 index 0000000..c4efa5b --- /dev/null +++ b/daemon/sh.c @@ -0,0 +1,319 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009 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 <stdbool.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "guestfs_protocol.h" +#include "daemon.h" +#include "actions.h" + +#include "ignore-value.h" + +GUESTFSD_EXT_CMD(str_cp, cp); +GUESTFSD_EXT_CMD(str_mount, mount); +GUESTFSD_EXT_CMD(str_umount, umount); + +#ifdef HAVE_ATTRIBUTE_CLEANUP +#define CLEANUP_BIND_STATE __attribute__((cleanup(free_bind_state))) +#define CLEANUP_RESOLVER_STATE __attribute__((cleanup(free_resolver_state))) +#else +#define CLEANUP_BIND_STATE +#define CLEANUP_RESOLVER_STATE +#endif + +struct bind_state { + bool mounted; + char *sysroot_dev; + char *sysroot_dev_pts; + char *sysroot_proc; + char *sysroot_selinux; + char *sysroot_sys; + char *sysroot_sys_fs_selinux; + bool dev_ok, dev_pts_ok, proc_ok, selinux_ok, sys_ok, sys_fs_selinux_ok; +}; + +struct resolver_state { + bool mounted; + char *sysroot_etc_resolv_conf; + char *sysroot_etc_resolv_conf_old; +}; + +/* While running the command, bind-mount /dev, /proc, /sys + * into the chroot. However we must be careful to unmount them + * afterwards because otherwise they would interfere with + * future mount and unmount operations. + * + * We deliberately allow these commands to fail silently, BUT + * if a mount fails, don't unmount the corresponding mount. + */ +static int +bind_mount (struct bind_state *bs) +{ + int r; + + memset (bs, 0, sizeof *bs); + + bs->sysroot_dev = sysroot_path ("/dev"); + bs->sysroot_dev_pts = sysroot_path ("/dev/pts"); + bs->sysroot_proc = sysroot_path ("/proc"); + bs->sysroot_selinux = sysroot_path ("/selinux"); + bs->sysroot_sys = sysroot_path ("/sys"); + bs->sysroot_sys_fs_selinux = sysroot_path ("/sys/fs/selinux"); + + if (bs->sysroot_dev == NULL || bs->sysroot_dev_pts == NULL || + bs->sysroot_proc == NULL || bs->sysroot_selinux == NULL || + bs->sysroot_sys == NULL || bs->sysroot_sys_fs_selinux == NULL) { + reply_with_perror ("malloc"); + free (bs->sysroot_dev); + free (bs->sysroot_dev_pts); + free (bs->sysroot_proc); + free (bs->sysroot_selinux); + free (bs->sysroot_sys); + free (bs->sysroot_sys_fs_selinux); + return -1; + } + + /* Note it is tempting to use --rbind here (to bind submounts). + * However I have not found a reliable way to unmount the same set + * of directories (umount -R does NOT work). + */ + r = command (NULL, NULL, str_mount, "--bind", "/dev", bs->sysroot_dev, NULL); + bs->dev_ok = r != -1; + r = command (NULL, NULL, str_mount, "--bind", "/dev/pts", bs->sysroot_dev_pts, NULL); + bs->dev_pts_ok = r != -1; + r = command (NULL, NULL, str_mount, "--bind", "/proc", bs->sysroot_proc, NULL); + bs->proc_ok = r != -1; + /* Note on the next line we have to bind-mount /sys/fs/selinux (appliance + * kernel) on top of /selinux (where guest is expecting selinux). + */ + r = command (NULL, NULL, str_mount, "--bind", "/sys/fs/selinux", bs->sysroot_selinux, NULL); + bs->selinux_ok = r != -1; + r = command (NULL, NULL, str_mount, "--bind", "/sys", bs->sysroot_sys, NULL); + bs->sys_ok = r != -1; + r = command (NULL, NULL, str_mount, "--bind", "/sys/fs/selinux", bs->sysroot_sys_fs_selinux, NULL); + bs->sys_fs_selinux_ok = r != -1; + + bs->mounted = true; + + return 0; +} + +static inline void +umount_ignore_fail (const char *path) +{ + ignore_value (command (NULL, NULL, str_umount, path, NULL)); +} + +static void +free_bind_state (struct bind_state *bs) +{ + if (bs->mounted) { + if (bs->sys_fs_selinux_ok) umount_ignore_fail (bs->sysroot_sys_fs_selinux); + free (bs->sysroot_sys_fs_selinux); + if (bs->sys_ok) umount_ignore_fail (bs->sysroot_sys); + free (bs->sysroot_sys); + if (bs->selinux_ok) umount_ignore_fail (bs->sysroot_selinux); + free (bs->sysroot_selinux); + if (bs->proc_ok) umount_ignore_fail (bs->sysroot_proc); + free (bs->sysroot_proc); + if (bs->dev_pts_ok) umount_ignore_fail (bs->sysroot_dev_pts); + free (bs->sysroot_dev_pts); + if (bs->dev_ok) umount_ignore_fail (bs->sysroot_dev); + free (bs->sysroot_dev); + bs->mounted = false; + } +} + +/* If the network is enabled, we want <sysroot>/etc/resolv.conf to + * reflect the contents of /etc/resolv.conf so that name resolution + * works. It would be nice to bind-mount the file (single file bind + * mounts are possible). However annoyingly that doesn't work for + * Ubuntu guests where the guest resolv.conf is a dangling symlink, + * and for reasons unknown mount tries to follow the symlink and + * fails (likely a bug). So this is a hack. Note we only invoke + * this if the network is enabled. + */ +static int +set_up_etc_resolv_conf (struct resolver_state *rs) +{ + struct stat statbuf; + + rs->sysroot_etc_resolv_conf_old = NULL; + + rs->sysroot_etc_resolv_conf = sysroot_path ("/etc/resolv.conf"); + + if (!rs->sysroot_etc_resolv_conf) { + reply_with_perror ("malloc"); + goto error; + } + + /* If /etc/resolv.conf exists, rename it to the backup file. Note + * that on Ubuntu it's a dangling symlink. + */ + if (lstat (rs->sysroot_etc_resolv_conf, &statbuf) == 0) { + size_t len = sysroot_len + 32; + char buf[len]; + + /* Make a random name for the backup file. */ + snprintf (buf, len, "%s/etc/XXXXXXXX", sysroot); + if (random_name (buf) == -1) { + reply_with_perror ("random_name"); + goto error; + } + rs->sysroot_etc_resolv_conf_old = strdup (buf); + if (!rs->sysroot_etc_resolv_conf_old) { + reply_with_perror ("strdup"); + goto error; + } + + if (verbose) + fprintf (stderr, "renaming %s to %s\n", rs->sysroot_etc_resolv_conf, + rs->sysroot_etc_resolv_conf_old); + + if (rename (rs->sysroot_etc_resolv_conf, + rs->sysroot_etc_resolv_conf_old) == -1) { + reply_with_perror ("rename: %s to %s", rs->sysroot_etc_resolv_conf, + rs->sysroot_etc_resolv_conf_old); + goto error; + } + } + + /* Now that the guest's <sysroot>/etc/resolv.conf is out the way, we + * can create our own copy of the appliance /etc/resolv.conf. + */ + ignore_value (command (NULL, NULL, str_cp, "/etc/resolv.conf", + rs->sysroot_etc_resolv_conf, NULL)); + + rs->mounted = true; + return 0; + + error: + free (rs->sysroot_etc_resolv_conf); + free (rs->sysroot_etc_resolv_conf_old); + return -1; +} + +static void +free_resolver_state (struct resolver_state *rs) +{ + if (rs->mounted) { + unlink (rs->sysroot_etc_resolv_conf); + + if (rs->sysroot_etc_resolv_conf_old) { + if (verbose) + fprintf (stderr, "renaming %s to %s\n", rs->sysroot_etc_resolv_conf_old, + rs->sysroot_etc_resolv_conf); + + if (rename (rs->sysroot_etc_resolv_conf_old, + rs->sysroot_etc_resolv_conf) == -1) + perror ("error: could not restore /etc/resolv.conf"); + + free (rs->sysroot_etc_resolv_conf_old); + } + + free (rs->sysroot_etc_resolv_conf); + rs->mounted = false; + } +} + +char * +do_command (char *const *argv) +{ + char *out; + CLEANUP_FREE char *err = NULL; + int r, flags; + CLEANUP_BIND_STATE struct bind_state bind_state = { .mounted = false }; + CLEANUP_RESOLVER_STATE struct resolver_state resolver_state + { .mounted = false }; + + /* We need a root filesystem mounted to do this. */ + NEED_ROOT (, return NULL); + + /* Conveniently, argv is already a NULL-terminated argv-style array + * of parameters, so we can pass it straight in to our internal + * commandv. We just have to check the list is non-empty. + */ + if (argv[0] == NULL) { + reply_with_error ("passed an empty list"); + return NULL; + } + + if (bind_mount (&bind_state) == -1) + return NULL; + if (enable_network) { + if (set_up_etc_resolv_conf (&resolver_state) == -1) + return NULL; + } + + flags = COMMAND_FLAG_DO_CHROOT; + + r = commandvf (&out, &err, flags, (const char * const *) argv); + + free_bind_state (&bind_state); + free_resolver_state (&resolver_state); + + if (r == -1) { + reply_with_error ("%s", err); + free (out); + return NULL; + } + + return out; /* Caller frees. */ +} + +char ** +do_command_lines (char *const *argv) +{ + CLEANUP_FREE char *out = NULL; + char **lines; + + out = do_command (argv); + if (out == NULL) + return NULL; + + lines = split_lines (out); + + if (lines == NULL) + return NULL; + + return lines; /* Caller frees. */ +} + +char * +do_sh (const char *cmd) +{ + const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; + + return do_command ((char **) argv); +} + +char ** +do_sh_lines (const char *cmd) +{ + const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; + + return do_command_lines ((char **) argv); +} diff --git a/po/POTFILES b/po/POTFILES index c6c277c..9719afd 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -26,7 +26,6 @@ daemon/btrfs.c daemon/cap.c daemon/checksum.c daemon/cmp.c -daemon/command.c daemon/compress.c daemon/copy.c daemon/cpio.c @@ -96,6 +95,7 @@ daemon/rsync.c daemon/scrub.c daemon/selinux.c daemon/sfdisk.c +daemon/sh.c daemon/sleep.c daemon/stat.c daemon/statvfs.c -- 2.5.0
Richard W.M. Jones
2016-Jan-21 15:48 UTC
[Libguestfs] [PATCH v3 2/6] daemon: Split out command() functions and CLEANUP_* macros into separate files.
Allows more sharing between the daemon and the inspection program. --- daemon/Makefile.am | 2 + daemon/cleanups.c | 80 ++++++++++ daemon/cleanups.h | 47 ++++++ daemon/command.c | 436 +++++++++++++++++++++++++++++++++++++++++++++++++++++ daemon/command.h | 41 +++++ daemon/daemon.h | 47 +----- daemon/guestfsd.c | 392 ----------------------------------------------- po/POTFILES | 2 + 8 files changed, 611 insertions(+), 436 deletions(-) create mode 100644 daemon/cleanups.c create mode 100644 daemon/cleanups.h create mode 100644 daemon/command.c create mode 100644 daemon/command.h diff --git a/daemon/Makefile.am b/daemon/Makefile.am index c1a33e3..a4e3df7 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -96,6 +96,8 @@ guestfsd_SOURCES = \ cap.c \ checksum.c \ cmp.c \ + command.c \ + command.h \ compress.c \ copy.c \ cpio.c \ diff --git a/daemon/cleanups.c b/daemon/cleanups.c new file mode 100644 index 0000000..5293ff2 --- /dev/null +++ b/daemon/cleanups.c @@ -0,0 +1,80 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 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 <unistd.h> + +#include <augeas.h> + +#include "cleanups.h" + +/* Use by the CLEANUP_* macros. Do not call these directly. */ +void +cleanup_free (void *ptr) +{ + free (* (void **) ptr); +} + +extern void free_strings (char **argv); + +void +cleanup_free_string_list (void *ptr) +{ + free_strings (* (char ***) ptr); +} + +void +cleanup_unlink_free (void *ptr) +{ + char *filename = * (char **) ptr; + + if (filename) { + unlink (filename); + free (filename); + } +} + +void +cleanup_close (void *ptr) +{ + int fd = * (int *) ptr; + + if (fd >= 0) + close (fd); +} + +void +cleanup_aug_close (void *ptr) +{ + augeas *aug = * (augeas **) ptr; + + if (aug != NULL) + aug_close (aug); +} + +struct stringsbuf; +extern void free_stringsbuf (struct stringsbuf *sb); + +void +cleanup_free_stringsbuf (void *ptr) +{ + free_stringsbuf ((struct stringsbuf *) ptr); +} diff --git a/daemon/cleanups.h b/daemon/cleanups.h new file mode 100644 index 0000000..b6ef2ff --- /dev/null +++ b/daemon/cleanups.h @@ -0,0 +1,47 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 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. + */ + +#ifndef GUESTFSD_CLEANUPS_H +#define GUESTFSD_CLEANUPS_H + +/* Use by the CLEANUP_* macros. */ +extern void cleanup_free (void *ptr); +extern void cleanup_free_string_list (void *ptr); +extern void cleanup_unlink_free (void *ptr); +extern void cleanup_close (void *ptr); +extern void cleanup_aug_close (void *ptr); +extern void cleanup_free_stringsbuf (void *ptr); + +#ifdef HAVE_ATTRIBUTE_CLEANUP +#define CLEANUP_FREE __attribute__((cleanup(cleanup_free))) +#define CLEANUP_FREE_STRING_LIST \ + __attribute__((cleanup(cleanup_free_string_list))) +#define CLEANUP_UNLINK_FREE __attribute__((cleanup(cleanup_unlink_free))) +#define CLEANUP_CLOSE __attribute__((cleanup(cleanup_close))) +#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) +#define CLEANUP_FREE_STRINGSBUF __attribute__((cleanup(cleanup_free_stringsbuf))) +#else +#define CLEANUP_FREE +#define CLEANUP_FREE_STRING_LIST +#define CLEANUP_UNLINK_FREE +#define CLEANUP_CLOSE +#define CLEANUP_AUG_CLOSE +#define CLEANUP_FREE_STRINGSBUF +#endif + +#endif /* GUESTFSD_CLEANUPS_H */ diff --git a/daemon/command.c b/daemon/command.c new file mode 100644 index 0000000..3e757aa --- /dev/null +++ b/daemon/command.c @@ -0,0 +1,436 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 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 <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <error.h> +#include <errno.h> + +#include "ignore-value.h" + +#include "command.h" +#include "cleanups.h" + +extern int verbose; + +extern const char *sysroot; +extern size_t sysroot_len; + +#ifndef MAX +# define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +/* For improved readability dealing with pipe arrays */ +#define PIPE_READ 0 +#define PIPE_WRITE 1 + +/* Easy ways to run external commands. For full documentation, see + * 'commandrvf' below. + */ +int +commandf (char **stdoutput, char **stderror, unsigned flags, + const char *name, ...) +{ + va_list args; + /* NB: Mustn't free the strings which are on the stack. */ + CLEANUP_FREE const char **argv = NULL; + char *s; + size_t i; + int r; + + /* Collect the command line arguments into an array. */ + i = 2; + argv = malloc (sizeof (char *) * i); + if (argv == NULL) { + perror ("malloc"); + return -1; + } + argv[0] = (char *) name; + argv[1] = NULL; + + va_start (args, name); + + while ((s = va_arg (args, char *)) != NULL) { + const char **p = realloc (argv, sizeof (char *) * (++i)); + if (p == NULL) { + perror ("realloc"); + va_end (args); + return -1; + } + argv = p; + argv[i-2] = s; + argv[i-1] = NULL; + } + + va_end (args); + + r = commandvf (stdoutput, stderror, flags, (const char * const*) argv); + + return r; +} + +/* Same as 'command', but we allow the status code from the + * subcommand to be non-zero, and return that status code. + * We still return -1 if there was some other error. + */ +int +commandrf (char **stdoutput, char **stderror, unsigned flags, + const char *name, ...) +{ + va_list args; + CLEANUP_FREE const char **argv = NULL; + char *s; + int i, r; + + /* Collect the command line arguments into an array. */ + i = 2; + argv = malloc (sizeof (char *) * i); + if (argv == NULL) { + perror ("malloc"); + return -1; + } + argv[0] = (char *) name; + argv[1] = NULL; + + va_start (args, name); + + while ((s = va_arg (args, char *)) != NULL) { + const char **p = realloc (argv, sizeof (char *) * (++i)); + if (p == NULL) { + perror ("realloc"); + va_end (args); + return -1; + } + argv = p; + argv[i-2] = s; + argv[i-1] = NULL; + } + + va_end (args); + + r = commandrvf (stdoutput, stderror, flags, argv); + + return r; +} + +/* Same as 'command', but passing an argv. */ +int +commandvf (char **stdoutput, char **stderror, unsigned flags, + char const *const *argv) +{ + int r; + + r = commandrvf (stdoutput, stderror, flags, (void *) argv); + if (r == 0) + return 0; + else + return -1; +} + +/* This is a more sane version of 'system(3)' for running external + * commands. It uses fork/execvp, so we don't need to worry about + * quoting of parameters, and it allows us to capture any error + * messages in a buffer. + * + * If stdoutput is not NULL, then *stdoutput will return the stdout + * of the command. + * + * If stderror is not NULL, then *stderror will return the stderr + * of the command. If there is a final \n character, it is removed + * so you can use the error string directly in a call to + * reply_with_error. + * + * Flags: + * + * COMMAND_FLAG_FOLD_STDOUT_ON_STDERR: For broken external commands + * that send error messages to stdout (hello, parted) but that don't + * have any useful stdout information, use this flag to capture the + * error messages in the *stderror buffer. If using this flag, + * you should pass stdoutput as NULL because nothing could ever be + * captured in that buffer. + * + * COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN: For running external + * commands on chrooted files correctly (see RHBZ#579608) specifying + * this flag causes another process to be forked which chroots into + * sysroot and just copies the input file to stdin of the specified + * command. The file descriptor is ORed with the flags, and that file + * descriptor is always closed by this function. See hexdump.c for an + * example of usage. + */ +int +commandrvf (char **stdoutput, char **stderror, unsigned flags, + char const* const *argv) +{ + size_t so_size = 0, se_size = 0; + int so_fd[2], se_fd[2]; + unsigned flag_copy_stdin = flags & COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN; + int flag_copy_fd = (int) (flags & COMMAND_FLAG_FD_MASK); + pid_t pid; + int r, quit, i; + fd_set rset, rset2; + char buf[256]; + char *p; + + if (stdoutput) *stdoutput = NULL; + if (stderror) *stderror = NULL; + + if (verbose) { + printf ("commandrvf: stdout=%s stderr=%s flags=0x%x\n", + stdoutput ? "y" : "n", stderror ? "y" : "n", flags); + fputs ("commandrvf: ", stdout); + fputs (argv[0], stdout); + for (i = 1; argv[i] != NULL; ++i) { + char quote; + + /* Do simple (and incorrect) quoting of the debug output. Real + * quoting is not necessary because we use execvp to run the + * command below. + */ + if (strchr (argv[i], '\'')) + quote = '"'; + else if (strchr (argv[i], '"')) + quote = '\''; + else if (strchr (argv[i], ' ')) + quote = '"'; + else + quote = 0; + + putchar (' '); + if (quote) putchar (quote); + fputs (argv[i], stdout); + if (quote) putchar (quote); + } + putchar ('\n'); + } + + /* Note: abort is used in a few places along the error paths early + * in this function. This is because (a) cleaning up correctly is + * very complex at these places and (b) abort is used when a + * resource problems is indicated which would be due to much more + * serious issues - eg. memory or file descriptor leaks. We + * wouldn't expect fork(2) or pipe(2) to fail in normal + * circumstances. + */ + + if (pipe (so_fd) == -1 || pipe (se_fd) == -1) { + error (0, errno, "pipe"); + abort (); + } + + pid = fork (); + if (pid == -1) { + error (0, errno, "fork"); + abort (); + } + + if (pid == 0) { /* Child process running the command. */ + signal (SIGALRM, SIG_DFL); + signal (SIGPIPE, SIG_DFL); + close (0); + if (flag_copy_stdin) { + if (dup2 (flag_copy_fd, STDIN_FILENO) == -1) { + perror ("dup2/flag_copy_fd"); + _exit (EXIT_FAILURE); + } + } else { + /* Set stdin to /dev/null. */ + if (open ("/dev/null", O_RDONLY) == -1) { + perror ("open: /dev/null"); + _exit (EXIT_FAILURE); + } + } + close (so_fd[PIPE_READ]); + close (se_fd[PIPE_READ]); + if (!(flags & COMMAND_FLAG_FOLD_STDOUT_ON_STDERR)) { + if (dup2 (so_fd[PIPE_WRITE], STDOUT_FILENO) == -1) { + perror ("dup2/so_fd[PIPE_WRITE]"); + _exit (EXIT_FAILURE); + } + } else { + if (dup2 (se_fd[PIPE_WRITE], STDOUT_FILENO) == -1) { + perror ("dup2/se_fd[PIPE_WRITE]"); + _exit (EXIT_FAILURE); + } + } + if (dup2 (se_fd[PIPE_WRITE], STDERR_FILENO) == -1) { + perror ("dup2/se_fd[PIPE_WRITE]"); + _exit (EXIT_FAILURE); + } + close (so_fd[PIPE_WRITE]); + close (se_fd[PIPE_WRITE]); + + if (flags & COMMAND_FLAG_DO_CHROOT && sysroot_len > 0) { + if (chroot (sysroot) == -1) { + perror ("chroot in sysroot"); + _exit (EXIT_FAILURE); + } + } + + if (chdir ("/") == -1) { + perror ("chdir"); + _exit (EXIT_FAILURE); + } + + execvp (argv[0], (void *) argv); + perror (argv[0]); + _exit (EXIT_FAILURE); + } + + /* Parent process. */ + close (so_fd[PIPE_WRITE]); + close (se_fd[PIPE_WRITE]); + + FD_ZERO (&rset); + FD_SET (so_fd[PIPE_READ], &rset); + FD_SET (se_fd[PIPE_READ], &rset); + + quit = 0; + while (quit < 2) { + again: + rset2 = rset; + r = select (MAX (so_fd[PIPE_READ], se_fd[PIPE_READ]) + 1, &rset2, + NULL, NULL, NULL); + if (r == -1) { + if (errno == EINTR) + goto again; + + perror ("select"); + quit: + if (stdoutput) { + free (*stdoutput); + *stdoutput = NULL; + } + if (stderror) { + free (*stderror); + /* Need to return non-NULL *stderror here since most callers + * will try to print and then free the err string. + * Unfortunately recovery from strdup failure here is not + * possible. + */ + *stderror = strdup ("error running external command, " + "see debug output for details"); + } + close (so_fd[PIPE_READ]); + close (se_fd[PIPE_READ]); + if (flag_copy_stdin) close (flag_copy_fd); + waitpid (pid, NULL, 0); + return -1; + } + + if (FD_ISSET (so_fd[PIPE_READ], &rset2)) { /* something on stdout */ + r = read (so_fd[PIPE_READ], buf, sizeof buf); + if (r == -1) { + perror ("read"); + goto quit; + } + if (r == 0) { FD_CLR (so_fd[PIPE_READ], &rset); quit++; } + + if (r > 0 && stdoutput) { + so_size += r; + p = realloc (*stdoutput, so_size); + if (p == NULL) { + perror ("realloc"); + goto quit; + } + *stdoutput = p; + memcpy (*stdoutput + so_size - r, buf, r); + } + } + + if (FD_ISSET (se_fd[PIPE_READ], &rset2)) { /* something on stderr */ + r = read (se_fd[PIPE_READ], buf, sizeof buf); + if (r == -1) { + perror ("read"); + goto quit; + } + if (r == 0) { FD_CLR (se_fd[PIPE_READ], &rset); quit++; } + + if (r > 0) { + if (verbose) + ignore_value (write (STDERR_FILENO, buf, r)); + + if (stderror) { + se_size += r; + p = realloc (*stderror, se_size); + if (p == NULL) { + perror ("realloc"); + goto quit; + } + *stderror = p; + memcpy (*stderror + se_size - r, buf, r); + } + } + } + } + + close (so_fd[PIPE_READ]); + close (se_fd[PIPE_READ]); + + /* Make sure the output buffers are \0-terminated. Also remove any + * trailing \n characters from the error buffer (not from stdout). + */ + if (stdoutput) { + void *q = realloc (*stdoutput, so_size+1); + if (q == NULL) { + perror ("realloc"); + free (*stdoutput); + } + *stdoutput = q; + if (*stdoutput) + (*stdoutput)[so_size] = '\0'; + } + if (stderror) { + void *q = realloc (*stderror, se_size+1); + if (q == NULL) { + perror ("realloc"); + free (*stderror); + } + *stderror = q; + if (*stderror) { + (*stderror)[se_size] = '\0'; + while (se_size > 0 && (*stderror)[se_size-1] == '\n') { + se_size--; + (*stderror)[se_size] = '\0'; + } + } + } + + if (flag_copy_stdin && close (flag_copy_fd) == -1) { + perror ("close"); + return -1; + } + + /* Get the exit status of the command. */ + if (waitpid (pid, &r, 0) != pid) { + perror ("waitpid"); + return -1; + } + + if (WIFEXITED (r)) { + return WEXITSTATUS (r); + } else + return -1; +} diff --git a/daemon/command.h b/daemon/command.h new file mode 100644 index 0000000..60f32b8 --- /dev/null +++ b/daemon/command.h @@ -0,0 +1,41 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 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. + */ + +#ifndef GUESTFSD_COMMAND_H +#define GUESTFSD_COMMAND_H + +#define command(out,err,name,...) commandf((out),(err),0,(name),__VA_ARGS__) +#define commandr(out,err,name,...) commandrf((out),(err),0,(name),__VA_ARGS__) +#define commandv(out,err,argv) commandvf((out),(err),0,(argv)) +#define commandrv(out,err,argv) commandrvf((out),(err),0,(argv)) + +#define COMMAND_FLAG_FD_MASK 0x0000ffff +#define COMMAND_FLAG_FOLD_STDOUT_ON_STDERR 0x00010000 +#define COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN 0x00020000 +#define COMMAND_FLAG_DO_CHROOT 0x00040000 + +extern int commandf (char **stdoutput, char **stderror, unsigned flags, + const char *name, ...) __attribute__((sentinel)); +extern int commandrf (char **stdoutput, char **stderror, unsigned flags, + const char *name, ...) __attribute__((sentinel)); +extern int commandvf (char **stdoutput, char **stderror, unsigned flags, + char const *const *argv); +extern int commandrvf (char **stdoutput, char **stderror, unsigned flags, + char const* const *argv); + +#endif /* GUESTFSD_COMMAND_H */ diff --git a/daemon/daemon.h b/daemon/daemon.h index 7c90917..994b47a 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -32,6 +32,9 @@ #include "guestfs-internal-all.h" +#include "cleanups.h" +#include "command.h" + /* Mountables */ typedef struct { @@ -117,28 +120,9 @@ extern char **split_lines (char *str); extern char **empty_list (void); -#define command(out,err,name,...) commandf((out),(err),0,(name),__VA_ARGS__) -#define commandr(out,err,name,...) commandrf((out),(err),0,(name),__VA_ARGS__) -#define commandv(out,err,argv) commandvf((out),(err),0,(argv)) -#define commandrv(out,err,argv) commandrvf((out),(err),0,(argv)) - #define __external_command __attribute__((__section__(".guestfsd_ext_cmds"))) #define GUESTFSD_EXT_CMD(___ext_cmd_var, ___ext_cmd_str) static const char ___ext_cmd_var[] __external_command = #___ext_cmd_str -#define COMMAND_FLAG_FD_MASK 0x0000ffff -#define COMMAND_FLAG_FOLD_STDOUT_ON_STDERR 0x00010000 -#define COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN 0x00020000 -#define COMMAND_FLAG_DO_CHROOT 0x00040000 - -extern int commandf (char **stdoutput, char **stderror, unsigned flags, - const char *name, ...) __attribute__((sentinel)); -extern int commandrf (char **stdoutput, char **stderror, unsigned flags, - const char *name, ...) __attribute__((sentinel)); -extern int commandvf (char **stdoutput, char **stderror, unsigned flags, - char const *const *argv); -extern int commandrvf (char **stdoutput, char **stderror, unsigned flags, - char const* const *argv); - extern int is_power_of_2 (unsigned long v); extern void trim (char *str); @@ -157,14 +141,6 @@ extern char *get_random_uuid (void); extern int asprintf_nowarn (char **strp, const char *fmt, ...); -/* Use by the CLEANUP_* macros. */ -extern void cleanup_free (void *ptr); -extern void cleanup_free_string_list (void *ptr); -extern void cleanup_unlink_free (void *ptr); -extern void cleanup_close (void *ptr); -extern void cleanup_aug_close (void *ptr); -extern void cleanup_free_stringsbuf (void *ptr); - /*-- in names.c (auto-generated) --*/ extern const char *function_names[]; @@ -452,21 +428,4 @@ is_zero (const char *buffer, size_t size) } \ } while (0) -#ifdef HAVE_ATTRIBUTE_CLEANUP -#define CLEANUP_FREE __attribute__((cleanup(cleanup_free))) -#define CLEANUP_FREE_STRING_LIST \ - __attribute__((cleanup(cleanup_free_string_list))) -#define CLEANUP_UNLINK_FREE __attribute__((cleanup(cleanup_unlink_free))) -#define CLEANUP_CLOSE __attribute__((cleanup(cleanup_close))) -#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) -#define CLEANUP_FREE_STRINGSBUF __attribute__((cleanup(cleanup_free_stringsbuf))) -#else -#define CLEANUP_FREE -#define CLEANUP_FREE_STRING_LIST -#define CLEANUP_UNLINK_FREE -#define CLEANUP_CLOSE -#define CLEANUP_AUG_CLOSE -#define CLEANUP_FREE_STRINGSBUF -#endif - #endif /* GUESTFSD_DAEMON_H */ diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c index b313534..00f53c6 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -68,10 +68,6 @@ GUESTFSD_EXT_CMD(str_uuidgen, uuidgen); # define O_CLOEXEC 0 #endif -/* For improved readability dealing with pipe arrays */ -#define PIPE_READ 0 -#define PIPE_WRITE 1 - /* If root device is an ext2 filesystem, this is the major and minor. * This is so we can ignore this device from the point of view of the * user, eg. in guestfs_list_devices and many other places. @@ -752,394 +748,6 @@ join_strings (const char *separator, char *const *argv) return r; } -/* Easy ways to run external commands. For full documentation, see - * 'commandrvf' below. - */ -int -commandf (char **stdoutput, char **stderror, unsigned flags, - const char *name, ...) -{ - va_list args; - /* NB: Mustn't free the strings which are on the stack. */ - CLEANUP_FREE const char **argv = NULL; - char *s; - size_t i; - int r; - - /* Collect the command line arguments into an array. */ - i = 2; - argv = malloc (sizeof (char *) * i); - if (argv == NULL) { - perror ("malloc"); - return -1; - } - argv[0] = (char *) name; - argv[1] = NULL; - - va_start (args, name); - - while ((s = va_arg (args, char *)) != NULL) { - const char **p = realloc (argv, sizeof (char *) * (++i)); - if (p == NULL) { - perror ("realloc"); - va_end (args); - return -1; - } - argv = p; - argv[i-2] = s; - argv[i-1] = NULL; - } - - va_end (args); - - r = commandvf (stdoutput, stderror, flags, (const char * const*) argv); - - return r; -} - -/* Same as 'command', but we allow the status code from the - * subcommand to be non-zero, and return that status code. - * We still return -1 if there was some other error. - */ -int -commandrf (char **stdoutput, char **stderror, unsigned flags, - const char *name, ...) -{ - va_list args; - CLEANUP_FREE const char **argv = NULL; - char *s; - int i, r; - - /* Collect the command line arguments into an array. */ - i = 2; - argv = malloc (sizeof (char *) * i); - if (argv == NULL) { - perror ("malloc"); - return -1; - } - argv[0] = (char *) name; - argv[1] = NULL; - - va_start (args, name); - - while ((s = va_arg (args, char *)) != NULL) { - const char **p = realloc (argv, sizeof (char *) * (++i)); - if (p == NULL) { - perror ("realloc"); - va_end (args); - return -1; - } - argv = p; - argv[i-2] = s; - argv[i-1] = NULL; - } - - va_end (args); - - r = commandrvf (stdoutput, stderror, flags, argv); - - return r; -} - -/* Same as 'command', but passing an argv. */ -int -commandvf (char **stdoutput, char **stderror, unsigned flags, - char const *const *argv) -{ - int r; - - r = commandrvf (stdoutput, stderror, flags, (void *) argv); - if (r == 0) - return 0; - else - return -1; -} - -/* This is a more sane version of 'system(3)' for running external - * commands. It uses fork/execvp, so we don't need to worry about - * quoting of parameters, and it allows us to capture any error - * messages in a buffer. - * - * If stdoutput is not NULL, then *stdoutput will return the stdout - * of the command. - * - * If stderror is not NULL, then *stderror will return the stderr - * of the command. If there is a final \n character, it is removed - * so you can use the error string directly in a call to - * reply_with_error. - * - * Flags: - * - * COMMAND_FLAG_FOLD_STDOUT_ON_STDERR: For broken external commands - * that send error messages to stdout (hello, parted) but that don't - * have any useful stdout information, use this flag to capture the - * error messages in the *stderror buffer. If using this flag, - * you should pass stdoutput as NULL because nothing could ever be - * captured in that buffer. - * - * COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN: For running external - * commands on chrooted files correctly (see RHBZ#579608) specifying - * this flag causes another process to be forked which chroots into - * sysroot and just copies the input file to stdin of the specified - * command. The file descriptor is ORed with the flags, and that file - * descriptor is always closed by this function. See hexdump.c for an - * example of usage. - */ -int -commandrvf (char **stdoutput, char **stderror, unsigned flags, - char const* const *argv) -{ - size_t so_size = 0, se_size = 0; - int so_fd[2], se_fd[2]; - unsigned flag_copy_stdin = flags & COMMAND_FLAG_CHROOT_COPY_FILE_TO_STDIN; - int flag_copy_fd = (int) (flags & COMMAND_FLAG_FD_MASK); - pid_t pid; - int r, quit, i; - fd_set rset, rset2; - char buf[256]; - char *p; - - if (stdoutput) *stdoutput = NULL; - if (stderror) *stderror = NULL; - - if (verbose) { - printf ("commandrvf: stdout=%s stderr=%s flags=0x%x\n", - stdoutput ? "y" : "n", stderror ? "y" : "n", flags); - fputs ("commandrvf: ", stdout); - fputs (argv[0], stdout); - for (i = 1; argv[i] != NULL; ++i) { - char quote; - - /* Do simple (and incorrect) quoting of the debug output. Real - * quoting is not necessary because we use execvp to run the - * command below. - */ - if (strchr (argv[i], '\'')) - quote = '"'; - else if (strchr (argv[i], '"')) - quote = '\''; - else if (strchr (argv[i], ' ')) - quote = '"'; - else - quote = 0; - - putchar (' '); - if (quote) putchar (quote); - fputs (argv[i], stdout); - if (quote) putchar (quote); - } - putchar ('\n'); - } - - /* Note: abort is used in a few places along the error paths early - * in this function. This is because (a) cleaning up correctly is - * very complex at these places and (b) abort is used when a - * resource problems is indicated which would be due to much more - * serious issues - eg. memory or file descriptor leaks. We - * wouldn't expect fork(2) or pipe(2) to fail in normal - * circumstances. - */ - - if (pipe (so_fd) == -1 || pipe (se_fd) == -1) { - error (0, errno, "pipe"); - abort (); - } - - pid = fork (); - if (pid == -1) { - error (0, errno, "fork"); - abort (); - } - - if (pid == 0) { /* Child process running the command. */ - signal (SIGALRM, SIG_DFL); - signal (SIGPIPE, SIG_DFL); - close (0); - if (flag_copy_stdin) { - if (dup2 (flag_copy_fd, STDIN_FILENO) == -1) { - perror ("dup2/flag_copy_fd"); - _exit (EXIT_FAILURE); - } - } else { - /* Set stdin to /dev/null. */ - if (open ("/dev/null", O_RDONLY) == -1) { - perror ("open: /dev/null"); - _exit (EXIT_FAILURE); - } - } - close (so_fd[PIPE_READ]); - close (se_fd[PIPE_READ]); - if (!(flags & COMMAND_FLAG_FOLD_STDOUT_ON_STDERR)) { - if (dup2 (so_fd[PIPE_WRITE], STDOUT_FILENO) == -1) { - perror ("dup2/so_fd[PIPE_WRITE]"); - _exit (EXIT_FAILURE); - } - } else { - if (dup2 (se_fd[PIPE_WRITE], STDOUT_FILENO) == -1) { - perror ("dup2/se_fd[PIPE_WRITE]"); - _exit (EXIT_FAILURE); - } - } - if (dup2 (se_fd[PIPE_WRITE], STDERR_FILENO) == -1) { - perror ("dup2/se_fd[PIPE_WRITE]"); - _exit (EXIT_FAILURE); - } - close (so_fd[PIPE_WRITE]); - close (se_fd[PIPE_WRITE]); - - if (flags & COMMAND_FLAG_DO_CHROOT && sysroot_len > 0) { - if (chroot (sysroot) == -1) { - perror ("chroot in sysroot"); - _exit (EXIT_FAILURE); - } - } - - if (chdir ("/") == -1) { - perror ("chdir"); - _exit (EXIT_FAILURE); - } - - execvp (argv[0], (void *) argv); - perror (argv[0]); - _exit (EXIT_FAILURE); - } - - /* Parent process. */ - close (so_fd[PIPE_WRITE]); - close (se_fd[PIPE_WRITE]); - - FD_ZERO (&rset); - FD_SET (so_fd[PIPE_READ], &rset); - FD_SET (se_fd[PIPE_READ], &rset); - - quit = 0; - while (quit < 2) { - again: - rset2 = rset; - r = select (MAX (so_fd[PIPE_READ], se_fd[PIPE_READ]) + 1, &rset2, - NULL, NULL, NULL); - if (r == -1) { - if (errno == EINTR) - goto again; - - perror ("select"); - quit: - if (stdoutput) { - free (*stdoutput); - *stdoutput = NULL; - } - if (stderror) { - free (*stderror); - /* Need to return non-NULL *stderror here since most callers - * will try to print and then free the err string. - * Unfortunately recovery from strdup failure here is not - * possible. - */ - *stderror = strdup ("error running external command, " - "see debug output for details"); - } - close (so_fd[PIPE_READ]); - close (se_fd[PIPE_READ]); - if (flag_copy_stdin) close (flag_copy_fd); - waitpid (pid, NULL, 0); - return -1; - } - - if (FD_ISSET (so_fd[PIPE_READ], &rset2)) { /* something on stdout */ - r = read (so_fd[PIPE_READ], buf, sizeof buf); - if (r == -1) { - perror ("read"); - goto quit; - } - if (r == 0) { FD_CLR (so_fd[PIPE_READ], &rset); quit++; } - - if (r > 0 && stdoutput) { - so_size += r; - p = realloc (*stdoutput, so_size); - if (p == NULL) { - perror ("realloc"); - goto quit; - } - *stdoutput = p; - memcpy (*stdoutput + so_size - r, buf, r); - } - } - - if (FD_ISSET (se_fd[PIPE_READ], &rset2)) { /* something on stderr */ - r = read (se_fd[PIPE_READ], buf, sizeof buf); - if (r == -1) { - perror ("read"); - goto quit; - } - if (r == 0) { FD_CLR (se_fd[PIPE_READ], &rset); quit++; } - - if (r > 0) { - if (verbose) - ignore_value (write (STDERR_FILENO, buf, r)); - - if (stderror) { - se_size += r; - p = realloc (*stderror, se_size); - if (p == NULL) { - perror ("realloc"); - goto quit; - } - *stderror = p; - memcpy (*stderror + se_size - r, buf, r); - } - } - } - } - - close (so_fd[PIPE_READ]); - close (se_fd[PIPE_READ]); - - /* Make sure the output buffers are \0-terminated. Also remove any - * trailing \n characters from the error buffer (not from stdout). - */ - if (stdoutput) { - void *q = realloc (*stdoutput, so_size+1); - if (q == NULL) { - perror ("realloc"); - free (*stdoutput); - } - *stdoutput = q; - if (*stdoutput) - (*stdoutput)[so_size] = '\0'; - } - if (stderror) { - void *q = realloc (*stderror, se_size+1); - if (q == NULL) { - perror ("realloc"); - free (*stderror); - } - *stderror = q; - if (*stderror) { - (*stderror)[se_size] = '\0'; - while (se_size > 0 && (*stderror)[se_size-1] == '\n') { - se_size--; - (*stderror)[se_size] = '\0'; - } - } - } - - if (flag_copy_stdin && close (flag_copy_fd) == -1) { - perror ("close"); - return -1; - } - - /* Get the exit status of the command. */ - if (waitpid (pid, &r, 0) != pid) { - perror ("waitpid"); - return -1; - } - - if (WIFEXITED (r)) { - return WEXITSTATUS (r); - } else - return -1; -} - /* Split an output string into a NULL-terminated list of lines, * wrapped into a stringsbuf. * Typically this is used where we have run an external command diff --git a/po/POTFILES b/po/POTFILES index 9719afd..aca174d 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -25,7 +25,9 @@ daemon/blockdev.c daemon/btrfs.c daemon/cap.c daemon/checksum.c +daemon/cleanups.c daemon/cmp.c +daemon/command.c daemon/compress.c daemon/copy.c daemon/cpio.c -- 2.5.0
Richard W.M. Jones
2016-Jan-21 15:48 UTC
[Libguestfs] [PATCH v3 3/6] inspection: Add rules compiler to the generator.
This adds a "prolog-inspired" compiler to the generator, which we can use to generate guestfs inspection. The compiler generates C code which should be reasonably efficient. Note this change requires ocamllex and ocamlyacc, but these are normally part of any OCaml distribution, since they are part of the OCaml compiler. We also import some GPL-licensed gnulib code for handling sets, which is OK because we're going to use these in a standalone program (in following commits). --- .gitignore | 4 + bootstrap | 2 + docs/guestfs-building.pod | 4 + generator/Makefile.am | 39 +- generator/rules_compiler.ml | 833 +++++++++++++++++++++++++++++++++++++++++++ generator/rules_compiler.mli | 21 ++ generator/rules_parser.mly | 143 ++++++++ generator/rules_scanner.mll | 113 ++++++ generator/types.ml | 67 ++++ generator/utils.ml | 16 + generator/utils.mli | 6 + m4/guestfs_ocaml.m4 | 2 + 12 files changed, 1248 insertions(+), 2 deletions(-) create mode 100644 generator/rules_compiler.ml create mode 100644 generator/rules_compiler.mli create mode 100644 generator/rules_parser.mly create mode 100644 generator/rules_scanner.mll diff --git a/.gitignore b/.gitignore index e34edb5..c035ca4 100644 --- a/.gitignore +++ b/.gitignore @@ -224,7 +224,11 @@ Makefile.in /generator/files-generated.txt /generator/generator /generator/.pod2text.data* +/generator/rules_parser.ml +/generator/rules_parser.mli +/generator/rules_scanner.ml /generator/stamp-generator +/generator/stamp-rules-parser /get-kernel/.depend /get-kernel/stamp-virt-get-kernel.pod /get-kernel/virt-get-kernel diff --git a/bootstrap b/bootstrap index 5df6f0f..f932c0c 100755 --- a/bootstrap +++ b/bootstrap @@ -36,6 +36,7 @@ gnulib_tool=$GNULIB_SRCDIR/gnulib-tool modules=' accept4 +array-oset areadlink areadlinkat arpa_inet @@ -97,6 +98,7 @@ warnings xalloc xalloc-die xgetcwd +xoset xstrtol xstrtoll xvasprintf diff --git a/docs/guestfs-building.pod b/docs/guestfs-building.pod index c39a8ea..8100970 100644 --- a/docs/guestfs-building.pod +++ b/docs/guestfs-building.pod @@ -104,6 +104,10 @@ I<Required>. Part of Perl core. =item OCaml findlib +=item ocamllex + +=item ocamlyacc + I<Required> if compiling from git. Optional (but recommended) if compiling from tarball. diff --git a/generator/Makefile.am b/generator/Makefile.am index f3d4852..bcdc20d 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -18,6 +18,9 @@ include $(top_srcdir)/subdir-rules.mk # In alphabetical order. +# +# Note we include ocamllex/ocamlyacc-generated files here, since +# we want to distribute these in the tarball for convenience. sources = \ actions.ml \ actions.mli \ @@ -48,6 +51,13 @@ sources = \ prepopts.mli \ python.ml \ ruby.ml \ + rules_compiler.ml \ + rules_compiler.mli \ + rules_parser.ml \ + rules_parser.mli \ + rules_parser.mly \ + rules_scanner.ml \ + rules_scanner.mll \ structs.ml \ structs.mli \ tests_c_api.ml \ @@ -88,6 +98,9 @@ objects = \ bindtests.cmo \ errnostring.cmo \ customize.cmo \ + rules_scanner.cmo \ + rules_parser.cmo \ + rules_compiler.cmo \ main.cmo EXTRA_DIST = $(sources) files-generated.txt @@ -101,6 +114,22 @@ if HAVE_OCAML generator: $(objects) $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -linkpkg $^ -o $@ +rules_parser.ml rules_parser.mli: stamp-rules-parser + +stamp-rules-parser: rules_parser.mly + rm -f $@ + $(OCAMLYACC) $< + touch $@ + +rules_scanner.ml: rules_scanner.mll + $(OCAMLLEX) $< + +# Apparently because rules_parser.mli and rules_scanner.ml may not +# exist before the Makefile is run, the pattern dependencies below +# don't add these rules automatically, so we have to be explicit. +rules_parser.cmi: rules_parser.mli +rules_scanner.cmi: rules_scanner.ml + # Dependencies. %.cmi: %.mli $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@ @@ -111,7 +140,7 @@ generator: $(objects) depend: .depend -.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml) +.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml) rules_parser.ml rules_parser.mli rules_scanner.ml rm -f $@ $@-t $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) $^ | \ $(SED) 's/ *$$//' | \ @@ -154,6 +183,12 @@ stamp-generator: generator CLEANFILES = $(noinst_DATA) $(noinst_PROGRAM) *.cmi *.cmo *~ -DISTCLEANFILES = .depend .pod2text.data.version.2 +DISTCLEANFILES = \ + .depend \ + .pod2text.data.version.2 \ + rules_parser.ml \ + rules_parser.mli \ + rules_scanner.ml \ + stamp-rules-parser SUFFIXES = .cmo .cmi .cmx .ml .mli .mll .mly diff --git a/generator/rules_compiler.ml b/generator/rules_compiler.ml new file mode 100644 index 0000000..bd6df30 --- /dev/null +++ b/generator/rules_compiler.ml @@ -0,0 +1,833 @@ +(* libguestfs + * Copyright (C) 2009-2016 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 + *) + +(* This is the compiler that turns inspection rules into C code. *) + +open Printf + +open Utils +open Types +open Pr +open Docstrings + +module StringSet = Set.Make (String) + +let (//) = Filename.concat + +type env = { + free_vars : string list; (* Variables which are free in the rule. *) + assign_vars : string list; (* Variables assigned by C code. *) + env_struct : string; (* Name of the C environment struct. *) +} + +let rec compile filename () + let prologue, rules, epilogue = parse filename in + type_check filename rules; + + generate_header ~extra_inputs:[filename] CStyle GPLv2plus; + + pr "\ +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <unistd.h> +#include <error.h> + +#include \"gl_oset.h\" +#include \"gl_xoset.h\" +#include \"gl_array_oset.h\" + +#include \"inspection.h\" + +#include \"guestfs-internal-all.h\" + +"; + + (match prologue with + | Some code -> insert_literal_code filename code + | None -> () + ); + + pr "\ +/* Disable a few warnings, so we can take a few short-cuts with the + * generated code. + */ +#pragma GCC diagnostic ignored \"-Wunused-variable\" +#pragma GCC diagnostic ignored \"-Wunused-macros\" +#pragma GCC diagnostic ignored \"-Wunused-function\" + +static gl_oset_t +new_string_set (void) +{ + /* Note that we don't need a dispose function because all the + * strings added to the set will be \"owned\" by other code, either + * static (all_strings) or owned by facts. + */ + return gl_oset_create_empty (GL_ARRAY_OSET, + (gl_setelement_compar_fn) strcmp, NULL); +} + +static void +add_all_strings (gl_oset_t set) +{ + size_t i; + + for (i = 0; all_strings[i] != NULL; ++i) + gl_oset_add (set, all_strings[i]); +} + +"; + + get_all_strings filename rules; + + (* Give each rule a unique number. The number is used for rule + * function names, environments, code functions and so on. + * eg: 'rule_0 ()', 'struct rule_0_env', 'rule_0_code_0 ()'. + *) + iteri ( + fun i rule -> + rule.rule_fn <- sprintf "rule_%d" i + ) rules; + + (* Create the environment struct for each rule. This contains all + * the variables either consumed or set by the function. + *) + let rules + List.map ( + fun rule -> + let env = compile_rule_environment filename rule in + (rule, env) + ) rules in + + (* Write all C code snippets to functions. *) + List.iter ( + fun (rule, env) -> + let j = ref 0 in + let rec loop = function + | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2 + | BoolCode code -> + code.code_fn <- sprintf "%s_code_%d" rule.rule_fn !j; + incr j; + compile_bool_code filename rule env code + | AssignCode (vs, row_count, code) -> + code.code_fn <- sprintf "%s_code_%d" rule.rule_fn !j; + incr j; + compile_assign_code filename rule env vs row_count code + | Term _ | Not _ | True | False -> () + in + loop rule.body + ) rules; + + (* Compile all rules into functions. *) + List.iter (fun (rule, env) -> compile_rule filename rule env) rules; + + pr "\ +void +rules (void) +{ + clear_true_facts (); + clear_false_facts (); + + /* Loop over all the rules until no more true facts can be added. */ + for (;;) { + size_t nr_true_facts = count_true_facts (); + +"; + + List.iter ( + fun (rule, env) -> + pr " if (verbose)\n"; + pr " printf (\"trying rule %%s\\n\", %S);\n" + (string_of_term rule.head); + pr " %s ();\n" rule.rule_fn; + pr "\n"; + ) rules; + + pr " /* Added a true fact during this iteration? */ + if (nr_true_facts == count_true_facts ()) + break; + } /* for (;;) */ +} + +"; + + (match epilogue with + | Some code -> insert_literal_code filename code + | None -> () + ); + + pr "/* EOF */\n" + +and insert_literal_code filename code + (* XXX This function gets the line number wrong. *) + let lineno = code.code_loc.Lexing.pos_lnum in + pr "#line %d \"%s\"\n" lineno filename; + pr "%s\n" code.code + +and get_all_strings filename rules + let rec loop = function + | True | False | BoolCode _ | AssignCode _ -> [] + | And (e1, e2) | Or (e1, e2) -> loop e1 @ loop e2 + | Term term | Not term -> get_term_strings term + and get_term_strings { term_args = args } + filter_map (function Variable _ -> None | Constant s -> Some s) args + in + let all_strings + List.map (fun rule -> get_term_strings rule.head @ loop rule.body) rules in + let all_strings = List.concat all_strings in + let all_strings = sort_uniq all_strings in + pr "const char *all_strings[] = {\n"; + pr " "; + let col = ref 0 in + List.iter ( + fun s -> + let len = String.length s in + if !col + len + 4 >= 72 then ( + col := 0; + pr "\n " + ); + pr "%S, " s; + col := !col + len + 4; + ) all_strings; + if !col > 0 then pr "\n"; + pr " NULL\n"; + pr "};\n"; + pr "\n" + +(* Work the environment of a single rule. Also write out the + * corresponding struct to the C file. + *) +and compile_rule_environment filename rule + (* The name of the C struct. *) + let env_struct = sprintf "%s_env" rule.rule_fn in + + (* Divide all the variables which appear in the rule into: + * - ones which we have to search for [free_vars], + * - ones which are going to be returned by a C expression within + * the body [assign_vars]. + * We can do this statically. + * These sets are non-overlapping, so we just need to check which + * variables are returned by C expressions, and do an additional + * check that no C expressions are returning the same variable. + *) + (* Get the complete list of vars ... *) + let free_vars = Hashtbl.create 13 in + (* ... from the head *) + List.iter ( + function + | Variable v -> + if Hashtbl.mem free_vars v then ( + eprintf "%s: variable '%s' appears two or more times in a rule\n" + filename v; + exit 1 + ); + Hashtbl.add free_vars v 1 + | Constant _ -> () + ) rule.head.term_args; + (* ... from the body *) + let rec loop = function + | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2 + | Term { term_args = args } | Not { term_args = args } -> + List.iter ( + function + | Variable v -> Hashtbl.replace free_vars v 1 + | Constant _ -> () + ) args + | True | False + | BoolCode _ | AssignCode _ -> () + in + loop rule.body; + + let assign_vars = Hashtbl.create 13 in + let rec loop = function + | True | False | Term _ | Not _ | BoolCode _ -> () + | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2 + | AssignCode (vs, _, _) -> + List.iter ( + fun v -> + Hashtbl.remove free_vars v; + if Hashtbl.mem assign_vars v then ( + eprintf "%s: variable '%s' appears two or more times in a C assignment expression in a rule\n" + filename v; + exit 1 + ); + Hashtbl.add assign_vars v 1 + ) vs + in + loop rule.body; + let free_vars = Hashtbl.fold (fun k _ ks -> k :: ks) free_vars [] in + let assign_vars = Hashtbl.fold (fun k _ ks -> k :: ks) assign_vars [] in + + (* Write out the C struct. *) + pr "/* Environment struct for rule %s */\n" (string_of_term rule.head); + pr "struct %s {\n" env_struct; + if free_vars <> [] then ( + pr " /* free variables */\n"; + List.iter (pr " char *%s;\n") free_vars + ); + if assign_vars <> [] then ( + pr " /* (rows of) variables assigned by C code */\n"; + pr " size_t nr_rows;\n"; + List.iter (pr " char **%s;\n") assign_vars + ); + pr "};\n"; + pr "\n"; + + (* Return the OCaml env. *) + { free_vars = free_vars; + assign_vars = assign_vars; + env_struct = env_struct; } + +(* Compile a single rule to C code. *) +and compile_rule filename rule env + (* For each free variable we need to find the possible values for that + * variable. If they appear within the body in a term like + * 'Foo(var)' then we can just look for matching facts and add + * them (at runtime). If they don't, then we start with the list + * of all strings culled from the source + all strings from all facts. + *) + let free_vars = List.map ( + fun v -> + let fact_lookups = ref [] in + let rec loop = function + | True | False | BoolCode _ | AssignCode _ -> () + | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2 + | Term { term_name = term_name; term_args = args } + | Not { term_name = term_name; term_args = args } -> + (* If this term contains this variable at some position, + * then save that in the list of 'facts'. + *) + iteri ( + fun arg_i -> + function + | Variable v' when v = v' -> + fact_lookups := (term_name, arg_i) :: !fact_lookups + | Variable _ | Constant _ -> () + ) args + in + loop rule.body; + let fact_lookups = sort_uniq !fact_lookups in + + v, fact_lookups + ) env.free_vars in + + pr "/* %s */\n" (string_of_term rule.head); + pr "static void\n"; + pr "%s (void)\n" rule.rule_fn; + pr "{\n"; + pr " struct %s env;\n" env.env_struct; + pr " bool added;\n"; + pr " size_t i;\n"; + List.iter ( + fun (v, _) -> + pr " gl_oset_t search_%s;\n" v; + pr " gl_oset_iterator_t iter_%s;\n" v; + ) free_vars; + pr "\n"; + + (* This is an optimization: If the rule contains no free variables, + * we only need to run it once. This even applies if there are + * assigned variables, because the C code is supposed to be pure, + * ie. produce the same result every time it is called. + *) + if free_vars = [] then ( + pr " /* Because this rule contains no free variables, we only need\n"; + pr " * to evaluate it once. This applies even if the rule runs\n"; + pr " * C code (see 'C code memoization' in guestfs-inspection(8)\n"; + pr " * for an explanation of why this is so).\n"; + pr " */\n"; + pr " static bool called = false;\n"; + pr " if (called)\n"; + pr " return;\n"; + pr " called = true;\n"; + pr "\n"; + ); + + if free_vars <> [] then + pr " /* Build the sets we will use for searching each free variable. */\n"; + List.iter ( + function + | v, [] -> + (* The variable doesn't appear in any expressions, so + * add a note to the source. Maybe emit a compiler warning? XXX + *) + pr " search_%s = new_string_set ();\n" v; + pr " /* Warning: variable '%s' is underspecified, so we will\n" v; + pr " * search over all strings from the source and all facts.\n"; + pr " */\n"; + pr " add_all_strings (search_%s);\n" v; + pr " add_all_fact_strings (search_%s);\n" v; + pr "\n" + | v, fact_lookups -> + pr " search_%s = new_string_set ();\n" v; + List.iter ( + fun (term_name, arg_i) -> + pr " add_strings_from_facts (search_%s, %S, %d);\n" + v term_name arg_i + ) fact_lookups; + pr "\n" + ) free_vars; + + (* Do a cartesian search over all [free_vars], substituting each set of + * variables, and evaluating the body. If it evaluates to true, + * then we will add a new true fact! (Or maybe several if we are + * dealing with a list assignment [()*={{...}}]). If it evaluates + * to false, we add a false fact. It's also possible that we + * cannot evaluate the rule at all, because it contains unknown + * facts, in which case we end up adding NO new facts. + *) + if free_vars <> [] then ( + pr " /* Perform cartesian search over free variables. */\n"; + + List.iter ( + fun (v, _) -> + pr " iter_%s = gl_oset_iterator (search_%s);\n" v v; + pr " while (gl_oset_iterator_next (&iter_%s,\n" v; + pr " (const void **)&env.%s)) {\n" v; + ) free_vars; + + ) else ( + (* If there are no free_vars, then we have to add a dummy loop + * around the next code so that the 'continue' statement can be used. + *) + pr " do {\n"; + ); + + (* Initialize any assign_vars in the env struct. Note that the + * free_vars are initialized by the iterator loops above. + *) + List.iter (pr " env.%s = NULL;\n") env.assign_vars; + if env.assign_vars <> [] then pr " env.nr_rows = 0;\n"; + + (* We can only do this optimization if assign_vars = [], + * because we don't know what the C code (returning those vars) + * may give us yet. XXX Actually we could be looser with this: + * we only need to check that the head term contains no assigned + * variables. + *) + if env.assign_vars = [] then ( + pr " {\n"; + pr " /* If the fact already exists, don't bother doing any work. */\n"; + pr " CLEANUP_FREE fact *fact = create_fact (%S, %d" + rule.head.term_name (List.length rule.head.term_args); + List.iter (function + | Variable v -> pr ", env.%s" v + | Constant s -> pr ", %S" s) + rule.head.term_args; + pr ");\n"; + pr "\n"; + pr " if (is_fact (true, fact) || is_fact (false, fact))\n"; + pr " continue;\n"; + pr " }\n"; + pr "\n"; + ); + + (* Evaluate the expression on the right hand side. *) + let rec eval result = function + | True -> + pr " %s = 1;\n" result + | False -> + pr " %s = 0;\n" result + | BoolCode code -> + pr " %s = %s (&env);\n" result code.code_fn + | AssignCode (_, _, code) -> + pr " %s (&env);\n" code.code_fn; + (* The result of AssignCode is always true (else it would + * have exited in the call above). Hence: + *) + pr " %s = 1;\n" result + | And (e1, e2) -> + let re1 = sprintf "r_%d" (unique ()) in + pr " int %s;\n" re1; + eval re1 e1; + pr " if (%s != 1)\n" re1; + pr " %s = %s;\n" result re1; + pr " else {\n"; + let re2 = sprintf "r_%d" (unique ()) in + pr " int %s;\n" re2; + eval re2 e2; + pr " %s = %s;\n" result re2; + pr " }\n"; + | Or (e1, e2) -> + let re1 = sprintf "r_%d" (unique ()) in + pr " int %s;\n" re1; + eval re1 e1; + pr " if (%s == 1)\n" re1; + pr " %s = %s;\n" result re1; + pr " else {\n"; + let re2 = sprintf "r_%d" (unique ()) in + pr " int %s;\n" re2; + eval re2 e2; + pr " %s = %s;\n" result re2; + pr " }\n"; + | Term term -> + pr " {\n"; + pr " CLEANUP_FREE fact *fact = create_fact (%S, %d" + term.term_name (List.length term.term_args); + List.iter ( + function + | Variable v -> pr ", env.%s" v + | Constant s -> pr ", %S" s + ) term.term_args; + pr ");\n"; + pr " %s = is_fact (true, fact);\n" result; + pr " }\n"; + | Not term -> + pr " {\n"; + pr " CLEANUP_FREE fact *fact = create_fact (%S, %d" + term.term_name (List.length term.term_args); + List.iter ( + function + | Variable v -> pr ", env.%s" v + | Constant s -> pr ", %S" s + ) term.term_args; + pr ");\n"; + pr " %s = is_fact (false, fact);\n" result; + pr " }\n"; + in + pr " /* Evaluate the RHS of the rule with this assignment of variables. */\n"; + pr " int result;\n"; + eval "result" rule.body; + pr " if (result == -1) /* not determined */ continue;\n"; + let make_fact ?i ?(indent = 2) () + let indent = spaces indent in + pr "%sCLEANUP_FREE fact *fact =\n" indent; + pr "%s create_fact (%S, %d" + indent rule.head.term_name (List.length rule.head.term_args); + List.iter ( + function + | Variable v -> + if not (List.mem v env.assign_vars) then + pr ", env.%s" v + else ( + let i = match i with Some i -> i | None -> assert false in + pr ", env.%s[%s]" v i + ) + | Constant s -> pr ", %S" s + ) rule.head.term_args; + pr ");\n"; + in + pr " if (result > 0) /* true */ {\n"; + if env.assign_vars = [] then ( + make_fact ~indent:4 (); + pr " added = add_fact (true, fact);\n"; + pr " if (added && verbose) {\n"; + pr " printf (\"added new fact \");\n"; + pr " print_fact (true, fact, stdout);\n"; + pr " printf (\"\\n\");\n"; + pr " }\n"; + ) else ( + pr " for (i = 0; i < env.nr_rows; ++i) {\n"; + make_fact ~i:"i" ~indent:6 (); + pr " added = add_fact (true, fact);\n"; + pr " if (added && verbose) {\n"; + pr " printf (\"added new fact \");\n"; + pr " print_fact (true, fact, stdout);\n"; + pr " printf (\"\\n\");\n"; + pr " }\n"; + pr " }\n"; + ); + pr " }\n"; + pr " if (result == 0) /* false */ {\n"; + if env.assign_vars = [] then ( + make_fact ~indent:4 (); + pr "\n"; + pr " added = add_fact (false, fact);\n"; + pr " if (added && verbose) {\n"; + pr " printf (\"added new fact \");\n"; + pr " print_fact (false, fact, stdout);\n"; + pr " printf (\"\\n\");\n"; + pr " }\n"; + ) else ( + pr " for (i = 0; i < env.nr_rows; ++i) {\n"; + make_fact ~i:"i" ~indent:6 (); + pr " added = add_fact (false, fact);\n"; + pr " if (added && verbose) {\n"; + pr " printf (\"added new fact \");\n"; + pr " print_fact (false, fact, stdout);\n"; + pr " printf (\"\\n\");\n"; + pr " }\n"; + pr " }\n"; + ); + pr " }\n"; + + (* Free any assign_vars. The free_vars don't have to be freed + * because the iterator loop handles them. + *) + List.iter ( + fun v -> + pr " for (i = 0; i < env.nr_rows; ++i)\n"; + pr " free (env.%s[i]);\n" v; + pr " free (env.%s);\n" v + ) env.assign_vars; + + if free_vars <> [] then ( + List.iter ( + fun (v, _) -> + pr " }\n"; + pr " gl_oset_iterator_free (&iter_%s);\n" v + ) (List.rev free_vars); + ) else ( + pr " } while (0);\n"; + ); + pr "\n"; + + List.iter ( + function + | v, _ -> + pr " gl_oset_free (search_%s);\n" v + ) free_vars; + + pr "}\n"; + pr "\n" + +(* Compile a BoolCode snippet from a rule into a function. *) +and compile_bool_code filename rule env code + (* Create a function which wraps the C code. *) + let code_wrapper_fn = sprintf "%s_wrapper" code.code_fn in + List.iter (fun v -> pr "#define %s (_env->%s)\n" v v) env.free_vars; + pr "static int\n"; + pr "%s (struct %s *_env)\n" code_wrapper_fn env.env_struct; + pr "{\n"; + insert_literal_code filename code; + pr "}\n"; + List.iter (pr "#undef %s\n") env.free_vars; + pr "\n"; + + (* Create the function itself. *) + pr "static int\n"; + pr "%s (struct %s *env)\n" code.code_fn env.env_struct; + pr "{\n"; + pr " int r;\n"; + pr "\n"; + pr " if (verbose)\n"; + pr " printf (\"running C function %%s:%%d\\n\",\n"; + pr " \"%s\", %d);\n" filename code.code_loc.Lexing.pos_lnum; + pr "\n"; + pr " r = %s (env);\n" code_wrapper_fn; + pr "\n"; + pr " /* If the C function returns -1, it causes us to exit at once. */\n"; + pr " if (r == -1)\n"; + pr " error (EXIT_FAILURE, 0,\n"; + pr " \"%%s:%%d: C function failed - see earlier errors\",\n"; + pr " \"%s\", %d);\n" + filename code.code_loc.Lexing.pos_lnum; + pr "\n"; + pr " return r;\n"; + pr "}\n"; + pr "\n"; + +(* Compile assignment code (AssignCode) snippet into a function. *) +and compile_assign_code filename rule env vs row_count code + (* Create a function for setting a row in the result. *) + let set_vars_fn = sprintf "%s_set_row" code.code_fn in + let set_vars_alias = sprintf "set_%s" (String.concat "_" vs) in + pr "static void\n"; + pr "%s (struct %s *_env, %s)\n" + set_vars_fn env.env_struct + (String.concat ", " + (List.map (sprintf "const char *%s") vs)); + pr "{\n"; + pr " size_t _i = _env->nr_rows;\n"; + pr "\n"; + List.iter ( + fun v -> + pr " _env->%s = realloc (_env->%s, (_i+1) * sizeof (char *));\n" v v; + pr " if (_env->%s == NULL)\n" v; + pr " error (EXIT_FAILURE, errno, \"realloc\");\n"; + pr " _env->%s[_i] = strdup (%s);\n" v v; + pr " if (_env->%s[_i] == NULL)\n" v; + pr " error (EXIT_FAILURE, errno, \"strdup\");\n" + ) vs; + pr " _env->nr_rows++;\n"; + pr "}\n"; + pr "\n"; + + (* Create a function which wraps the C code. *) + let code_wrapper_fn = sprintf "%s_wrapper" code.code_fn in + List.iter (fun v -> pr "#define %s (_env->%s)\n" v v) env.free_vars; + pr "#define %s(%s) %s (_env, %s)\n" + set_vars_alias (String.concat ", " vs) + set_vars_fn (String.concat ", " (List.map (fun v -> "("^v^")") vs)); + pr "static int\n"; + pr "%s (struct %s *_env)\n" code_wrapper_fn env.env_struct; + pr "{\n"; + insert_literal_code filename code; + pr "}\n"; + List.iter (pr "#undef %s\n") env.free_vars; + pr "#undef %s\n" set_vars_alias; + pr "\n"; + + (* Create the function itself. *) + pr "static void\n"; + pr "%s (struct %s *env)\n" code.code_fn env.env_struct; + pr "{\n"; + pr " int r;\n"; + pr "\n"; + pr " if (verbose)\n"; + pr " printf (\"running C function %%s:%%d\\n\",\n"; + pr " \"%s\", %d);\n" filename code.code_loc.Lexing.pos_lnum; + pr "\n"; + pr " r = %s (env);\n" code_wrapper_fn; + pr "\n"; + pr " /* If the C function returns -1, it causes us to exit at once. */\n"; + pr " if (r == -1)\n"; + pr " error (EXIT_FAILURE, 0,\n"; + pr " \"%%s:%%d: C function failed - see earlier errors\",\n"; + pr " \"%s\", %d);\n" + filename code.code_loc.Lexing.pos_lnum; + pr "\n"; + pr " /* Check the set_* function was called the expected number\n"; + pr " * of times.\n"; + pr " */\n"; + (match row_count with + | RowsOne -> + pr " if (env->nr_rows != 1)\n" + | RowsZeroOrMore -> + pr " if (0) /* no check necessary for (var)* assignment */\n" + | RowsZeroOrOne -> + pr " if (env->nr_rows > 1)\n" + | RowsOneOrMore -> + pr " if (env->nr_rows < 1)\n" + ); + pr " error (EXIT_FAILURE, 0,\n"; + pr " \"%%s:%%d: C function called %%s incorrect number of times (%%zu)\",\n"; + pr " \"%s\", %d, \"%s\", env->nr_rows);\n" + filename code.code_loc.Lexing.pos_lnum set_vars_alias; + pr "}\n"; + pr "\n"; + +(* Parse the input. *) +and parse filename + let lexbuf = Lexing.from_channel (open_in filename) in + let chunks = ref [] in + (try + while true do + let chunk = Rules_parser.chunk Rules_scanner.token lexbuf in + chunks := chunk :: !chunks + done + with + | End_of_file -> () + | Rules_scanner.Error (msg, _, lineno, charno) -> + eprintf "%s: %d: %d: %s\n" filename lineno charno msg; + exit 1 + | Parsing.Parse_error -> + let p = Lexing.lexeme_start_p lexbuf in + eprintf "%s: %d: %d: syntax error\n" + filename + p.Lexing.pos_lnum + (p.Lexing.pos_cnum - p.Lexing.pos_bol); + exit 1 + ); + + (* Allow only the first and last chunk to be code (optional prologue + * and epilogue). The rest must be rules. + *) + let rev_chunks = !chunks in + + let epilogue, rev_chunks + match rev_chunks with + | CodeChunk epilogue :: chunks -> Some epilogue, chunks + | chunks -> None, chunks in + + let chunks = List.rev rev_chunks in + + let prologue, chunks + match chunks with + | CodeChunk prologue :: chunks -> Some prologue, chunks + | chunks -> None, chunks in + + let rules = List.map ( + function + | RuleChunk rule -> rule + | CodeChunk { code_loc = code_loc } -> + eprintf "%s: %d: syntax error: prologue and epilogue can only appear at the beginning or end of the input file\n" + filename code_loc.Lexing.pos_lnum; + exit 1 + ) chunks in + + prologue, rules, epilogue + +(* Minimal type checking. *) +and type_check filename rules + check_term_rhs filename rules; + check_term_arity filename rules + +(* If a term appears on the right hand side in any expression, then + * the term must also appear on the left hand side of a rule. + *) +and check_term_rhs filename rules + let names = List.map (fun { head = { term_name = name } } -> name) rules in + let names + List.fold_left (fun set x -> StringSet.add x set) StringSet.empty names in + + let errors = ref 0 in + List.iter ( + fun { body = body } -> + visit_terms ( + fun { term_name = name } -> + if not (StringSet.mem name names) then ( + eprintf "%s: '%s' appears in a rule expression, but does not appear on the left hand side of any rule. Maybe there is a typo?\n" + filename name; + incr errors + ) + ) body + ) rules; + if !errors > 0 then exit 1 + +(* Check the arity of terms is the same wherever they appear. *) +and check_term_arity filename rules + let hash = Hashtbl.create (List.length rules) in (* name -> arity *) + + let errors = ref 0 in + + let check_arity { term_name = name; term_args = args } + let arity = List.length args in + try + let expected_arity = Hashtbl.find hash name in + if arity <> expected_arity then ( + eprintf "%s: '%s' has different number of parameters (has %d, expected %d). It must have the same number of parameters throughout the program.\n" + filename name arity expected_arity; + incr errors + ) + with + (* The first time we've seen this term. *) + Not_found -> Hashtbl.add hash name arity + in + + List.iter ( + fun { head = head; body = body } -> + check_arity head; + visit_terms check_arity body + ) rules; + + if !errors > 0 then exit 1 + +and visit_terms f = function + | And (e1, e2) + | Or (e1, e2) -> visit_terms f e1; visit_terms f e2 + | Term t + | Not t -> f t + | True | False | BoolCode _ | AssignCode _ -> () + +and unique + let i = ref 0 in + fun () -> incr i; !i diff --git a/generator/rules_compiler.mli b/generator/rules_compiler.mli new file mode 100644 index 0000000..2bc5274 --- /dev/null +++ b/generator/rules_compiler.mli @@ -0,0 +1,21 @@ +(* libguestfs + * Copyright (C) 2009-2015 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 + *) + +(* This is the compiler that turns inspection rules into C code. *) + +val compile : string -> unit -> unit diff --git a/generator/rules_parser.mly b/generator/rules_parser.mly new file mode 100644 index 0000000..8dc595f --- /dev/null +++ b/generator/rules_parser.mly @@ -0,0 +1,143 @@ +/* libguestfs -*- text -*- + * Copyright (C) 2009-2016 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 + */ + +%{ +open Types +%} + +%token <string> STRING /* string literal */ +%token <string> UID /* uppercase identifier */ +%token <string> LID /* lowercase identifier */ + +%token TRUE /* true (keyword) */ +%token FALSE /* false (keyword) */ + +%token LPAREN RPAREN /* ( ... ) */ +%token <string> CODE /* {{ .. }} containing C code */ +%token STAR /* * (zero or more rows) */ +%token QUESTION /* ? (zero or one row) */ +%token PLUS /* + (one or more rows) */ +%token DOT /* . */ +%token IMPLIC /* :- (implication) */ +%token COMMA /* , (AND operator) */ +%token SEMI /* ; (OR operator) */ +%token NOT /* ! */ +%token EQUALS /* = */ + +/* These operators are arranged from lowest to highest precedence. */ +%left IMPLIC +%left SEMI +%left COMMA +%nonassoc NOT + +%start chunk +%type <Types.chunk> chunk +%type <Types.rule> rule + +%% + +chunk: CODE + { CodeChunk { code = $1; + code_loc = symbol_start_pos (); + code_fn = "" } } + | rule + { RuleChunk $1 } + ; + +rules: /* empty */ + { [] } + | rule rules + { $1 :: $2 } + +rule: head DOT + { { head = $1; body = True; + rule_loc = symbol_start_pos (); rule_fn = "" } } + | head IMPLIC body DOT + { { head = $1; body = $3; + rule_loc = symbol_start_pos (); rule_fn = "" } } + ; + +head: term + { $1 } + ; + +term: UID + { { term_name = $1; term_args = [] } } + | UID LPAREN term_args RPAREN + { { term_name = $1; term_args = $3 } } + ; + +term_args: + term_arg + { [ $1 ] } + | term_arg COMMA term_args + { $1 :: $3 } + ; + +term_arg: + LID + { Variable $1 } + | STRING + { Constant $1 } + ; + +body: expr + { $1 } + ; + +expr: TRUE + { True } + | FALSE + { False } + | term + { Term $1 } + | CODE + { BoolCode { code = $1; + code_loc = symbol_start_pos (); + code_fn = "" } } + | LPAREN result_bindings RPAREN EQUALS CODE + { AssignCode ($2, RowsOne, + { code = $5; + code_loc = symbol_start_pos (); code_fn = "" }) } + | LPAREN result_bindings RPAREN STAR EQUALS CODE + { AssignCode ($2, RowsZeroOrMore, + { code = $6; + code_loc = symbol_start_pos (); code_fn = "" }) } + | LPAREN result_bindings RPAREN QUESTION EQUALS CODE + { AssignCode ($2, RowsZeroOrOne, + { code = $6; + code_loc = symbol_start_pos (); code_fn = "" }) } + | LPAREN result_bindings RPAREN PLUS EQUALS CODE + { AssignCode ($2, RowsOneOrMore, + { code = $6; + code_loc = symbol_start_pos (); code_fn = "" }) } + | NOT term + { Not $2 } + | expr COMMA expr + { And ($1, $3) } + | expr SEMI expr + { Or ($1, $3) } + | LPAREN expr RPAREN + { $2 } + ; + +result_bindings: + LID + { [ $1 ] } + | LID COMMA result_bindings + { $1 :: $3 } diff --git a/generator/rules_scanner.mll b/generator/rules_scanner.mll new file mode 100644 index 0000000..5b32aaf --- /dev/null +++ b/generator/rules_scanner.mll @@ -0,0 +1,113 @@ +(* libguestfs -*- text -*- + * Copyright (C) 2009-2016 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 + *) + +{ +open Rules_parser + +let string_of_lexbuf = Lexing.lexeme + +(* Errors raised by the lexer. *) +exception Error of string * string * int * int + +let raise_error lexbuf msg + let p = Lexing.lexeme_start_p lexbuf in + raise (Error (msg, p.Lexing.pos_fname, + p.Lexing.pos_lnum, + p.Lexing.pos_cnum - p.Lexing.pos_bol)) + +(* Store "..." strings. *) +let string_buf = Buffer.create 256 +let reset_string_buffer () = Buffer.clear string_buf +let store_string_char c = Buffer.add_char string_buf c +let get_string_buffer () = Buffer.contents string_buf + +let char_for_backslash = function + | 'n' -> '\010' + | 'r' -> '\013' + | 'b' -> '\008' + | 't' -> '\009' + | c -> c + +(* Store {{ CODE }} sections. *) +let code_buf = Buffer.create 256 +let reset_code_buffer () = Buffer.clear code_buf +let store_code_char c = Buffer.add_char code_buf c +let get_code_buffer () = Buffer.contents code_buf +} + +(* Characters that can appear within an identifier (after the first + * character which is treated specially below). + *) +let id_char = ['a'-'z' 'A'-'Z' '0'-'9' '_'] + +(* Whitespace. *) +let ws = [' ' '\t'] + +(* Backslash escapes within strings. *) +let backslash_escapes = ['\\' '\'' '"' 'n' 't' 'b' 'r'] + +rule token = parse + | "/*" { comment lexbuf; token lexbuf } + | '(' { LPAREN } + | ')' { RPAREN } + | '*' { STAR } + | '?' { QUESTION } + | '+' { PLUS } + | '.' { DOT } + | ":-" { IMPLIC } + | ',' { COMMA } + | ';' { SEMI } + | '!' { NOT } + | '=' { EQUALS } + | '"' { reset_string_buffer (); + string lexbuf; + STRING (get_string_buffer ()) } + | "{{" { reset_code_buffer (); + code lexbuf; + CODE (get_code_buffer ()) } + | "true" { TRUE } + | "false" { FALSE } + | ['A'-'Z'] id_char* { UID (string_of_lexbuf lexbuf) } + | ['a'-'z' '_'] id_char* { LID (string_of_lexbuf lexbuf) } + | '\n' { Lexing.new_line lexbuf; token lexbuf } + | ws { token lexbuf } + | eof { raise End_of_file } + | _ { raise_error lexbuf "unexpected character in input" } + +(* Discard C-style comments. *) +and comment = parse + | "*/" { () } + | eof { raise_error lexbuf "unterminated comment" } + | '\n' { Lexing.new_line lexbuf; comment lexbuf } + | _ { comment lexbuf } + +(* Store "..." strings. *) +and string = parse + | '"' { () } + | eof { raise_error lexbuf "unterminated string" } + | '\n' { raise_error lexbuf "strings cannot contain newline characters" } + | '\\' (backslash_escapes as c) + { store_string_char (char_for_backslash c); string lexbuf } + | _ as c { store_string_char c; string lexbuf } + +(* Store {{ ... }} (CODE) sections containing C code. *) +and code = parse + | "}}" { () } + | eof { raise_error lexbuf "unterminated code section" } + | '\n' as c { Lexing.new_line lexbuf; store_code_char c; code lexbuf } + | _ as c { store_code_char c; code lexbuf } diff --git a/generator/types.ml b/generator/types.ml index 7fa65c9..ba8341c 100644 --- a/generator/types.ml +++ b/generator/types.ml @@ -18,6 +18,8 @@ (* Please read generator/README first. *) +open Printf + (* Types used to describe the API. *) type style = ret * args * optargs @@ -421,3 +423,68 @@ type call_optargt | CallOInt64 of string * int64 | CallOString of string * string | CallOStringList of string * string list + +(* Used by the rules compiler. *) + +type chunk = RuleChunk of rule | CodeChunk of code + +and rule = { + head : term; + body : expr; + rule_loc : Lexing.position; + mutable rule_fn : string; +} +(* The type of a parsed rule from the source. *) + +and term = { term_name : string; term_args : term_arg list } + +and term_arg = Variable of string | Constant of string + +and expr + | True (* used for facts *) + | False (* false (keyword) *) + | Term of term + | Not of term (* ! term *) + | And of expr * expr (* expr, expr *) + | Or of expr * expr (* expr; expr *) + | BoolCode of code (* {{ ... }} *) + | AssignCode of string list * row_count * code (* (a,b)={{ ... }} *) + +and code = { + code : string; + code_loc : Lexing.position; + mutable code_fn : string; +} + +and row_count = RowsOne | RowsZeroOrMore | RowsOneOrMore | RowsZeroOrOne + +let rec string_of_rule { head = head; body = body } + sprintf "%s :-\n\t%s." (string_of_term head) (string_of_expr body) + +and string_of_term = function + | { term_name = term_name; term_args = [] } -> + sprintf "%s" term_name + | { term_name = term_name; term_args = args } -> + sprintf "%s(%s)" term_name + (String.concat ", " (List.map string_of_term_arg args)) + +and string_of_term_arg = function + | Variable s -> s + | Constant s -> sprintf "%S" s + +and string_of_expr = function + | True -> "true" + | False -> "true" + | Term term -> string_of_term term + | Not term -> sprintf "!%s" (string_of_term term) + | And (e1, e2) -> sprintf "(%s,%s)" (string_of_expr e1) (string_of_expr e2) + | Or (e1, e2) -> sprintf "(%s;%s)" (string_of_expr e1) (string_of_expr e2) + | BoolCode _ -> "{{ // code }}" + | AssignCode (bindings, RowsOne, _) -> + sprintf "(%s)={{ // code }}" (String.concat ", " bindings) + | AssignCode (bindings, RowsZeroOrMore, _) -> + sprintf "(%s)*={{ // code }}" (String.concat ", " bindings) + | AssignCode (bindings, RowsOneOrMore, _) -> + sprintf "(%s)+={{ // code }}" (String.concat ", " bindings) + | AssignCode (bindings, RowsZeroOrOne, _) -> + sprintf "(%s)?={{ // code }}" (String.concat ", " bindings) diff --git a/generator/utils.ml b/generator/utils.ml index 6eff6ad..bb541ba 100644 --- a/generator/utils.ml +++ b/generator/utils.ml @@ -231,6 +231,22 @@ let mapi f xs in loop 0 xs +let uniq ?(cmp = Pervasives.compare) xs + let rec loop acc = function + | [] -> acc + | [x] -> x :: acc + | x :: (y :: _ as xs) when cmp x y = 0 -> + loop acc xs + | x :: (y :: _ as xs) -> + loop (x :: acc) xs + in + List.rev (loop [] xs) + +let sort_uniq ?(cmp = Pervasives.compare) xs + let xs = List.sort cmp xs in + let xs = uniq ~cmp xs in + xs + let count_chars c str let count = ref 0 in for i = 0 to String.length str - 1 do diff --git a/generator/utils.mli b/generator/utils.mli index aec1f71..96d0bd5 100644 --- a/generator/utils.mli +++ b/generator/utils.mli @@ -84,6 +84,12 @@ val iteri : (int -> 'a -> unit) -> 'a list -> unit val mapi : (int -> 'a -> 'b) -> 'a list -> 'b list +val uniq : ?cmp:('a -> 'a -> int) -> 'a list -> 'a list + (** Uniquify a list (the list must be sorted first). *) + +val sort_uniq : ?cmp:('a -> 'a -> int) -> 'a list -> 'a list + (** Sort and uniquify a list. *) + val count_chars : char -> string -> int (** Count number of times the character occurs in string. *) diff --git a/m4/guestfs_ocaml.m4 b/m4/guestfs_ocaml.m4 index 346779c..829906b 100644 --- a/m4/guestfs_ocaml.m4 +++ b/m4/guestfs_ocaml.m4 @@ -29,6 +29,8 @@ AS_IF([test "x$enable_ocaml" != "xno"],[ OCAMLFIND AC_PROG_OCAML AC_PROG_FINDLIB + AC_PROG_OCAMLLEX + AC_PROG_OCAMLYACC dnl OCaml >= 3.11 is required. AC_MSG_CHECKING([if OCaml version >= 3.11]) -- 2.5.0
Richard W.M. Jones
2016-Jan-21 15:48 UTC
[Libguestfs] [PATCH v3 4/6] inspection: Add inspection directory, rules and supporting code.
Using the rules compiler added in the previous commit, create an inspection program ("guestfs-inspection"). This will run inside the appliance. It is partly written in the Prolog-inspired language, with C supporting functions. For more details, read the guestfs-inspection(8) man page added in this commit. --- .gitignore | 4 + Makefile.am | 2 + appliance/Makefile.am | 3 +- configure.ac | 1 + docs/guestfs-hacking.pod | 6 + generator/Makefile.am | 2 +- generator/main.ml | 4 + inspection/Makefile.am | 92 ++++++ inspection/detect.c | 130 +++++++++ inspection/facts.c | 318 ++++++++++++++++++++ inspection/guestfs-inspection.pod | 450 +++++++++++++++++++++++++++++ inspection/inspection.c | 106 +++++++ inspection/inspection.h | 79 +++++ inspection/inspection.rules | 478 ++++++++++++++++++++++++++++++ inspection/mount.c | 591 ++++++++++++++++++++++++++++++++++++++ inspection/stringsbuf.c | 254 ++++++++++++++++ inspection/stringsbuf.h | 56 ++++ po/POTFILES | 5 + src/guestfs.pod | 1 + 19 files changed, 2580 insertions(+), 2 deletions(-) create mode 100644 inspection/Makefile.am create mode 100644 inspection/detect.c create mode 100644 inspection/facts.c create mode 100644 inspection/guestfs-inspection.pod create mode 100644 inspection/inspection.c create mode 100644 inspection/inspection.h create mode 100644 inspection/inspection.rules create mode 100644 inspection/mount.c create mode 100644 inspection/stringsbuf.c create mode 100644 inspection/stringsbuf.h diff --git a/.gitignore b/.gitignore index c035ca4..0106ec3 100644 --- a/.gitignore +++ b/.gitignore @@ -251,6 +251,10 @@ Makefile.in /haskell/Guestfs030Config /haskell/Guestfs050LVCreate /haskell/Guestfs.hs +/inspection/guestfs-inspection +/inspection/guestfs-inspection.8 +/inspection/rules.c +/inspection/stamp-guestfs-inspection.pod /inspector/actual-*.xml /inspector/stamp-virt-inspector.pod /inspector/test-xmllint.sh diff --git a/Makefile.am b/Makefile.am index ba99feb..11f5e78 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,6 +42,7 @@ SUBDIRS += src docs examples po # The daemon and the appliance. if ENABLE_DAEMON +SUBDIRS += inspection SUBDIRS += daemon SUBDIRS += tests/daemon endif @@ -290,6 +291,7 @@ all-local: find $(DIST_SUBDIRS) -name '*.c' -o -name '*.pl' -o -name '*.pm' | \ grep -v -E '^(examples|gnulib|gobject/docs|perl/(blib|examples)|po-docs|tests|test-data)/' | \ grep -v -E '/((guestfs|rc)_protocol\.c)$$' | \ + grep -v -E '^inspection/rules\.c$$' | \ grep -v -E '^python/utils\.c$$' | \ LC_ALL=C sort > po/POTFILES cd $(srcdir); \ diff --git a/appliance/Makefile.am b/appliance/Makefile.am index d8fb15b..eb1f4d3 100644 --- a/appliance/Makefile.am +++ b/appliance/Makefile.am @@ -72,11 +72,12 @@ packagelist: packagelist.in Makefile cmp -s $@ $@-t || mv $@-t $@ rm -f $@-t -supermin.d/daemon.tar.gz: ../daemon/guestfsd guestfs_lvm_conf.aug guestfs_shadow.aug +supermin.d/daemon.tar.gz: ../daemon/guestfsd ../inspection/guestfs-inspection guestfs_lvm_conf.aug guestfs_shadow.aug rm -f $@ $@-t rm -rf tmp-d mkdir -p tmp-d$(DAEMON_SUPERMIN_DIR) tmp-d/etc tmp-d/usr/share/guestfs ln ../daemon/guestfsd tmp-d$(DAEMON_SUPERMIN_DIR)/guestfsd + ln ../inspection/guestfs-inspection tmp-d$(DAEMON_SUPERMIN_DIR)/guestfs-inspection ln $(srcdir)/guestfs_lvm_conf.aug tmp-d/usr/share/guestfs/guestfs_lvm_conf.aug ln $(srcdir)/guestfs_shadow.aug tmp-d/usr/share/guestfs/guestfs_shadow.aug ( cd tmp-d && tar zcf - * ) > $@-t diff --git a/configure.ac b/configure.ac index 2bd1640..0915b83 100644 --- a/configure.ac +++ b/configure.ac @@ -205,6 +205,7 @@ AC_CONFIG_FILES([Makefile golang/Makefile golang/examples/Makefile haskell/Makefile + inspection/Makefile inspector/Makefile java/Makefile java/examples/Makefile diff --git a/docs/guestfs-hacking.pod b/docs/guestfs-hacking.pod index 3602180..974c0d6 100644 --- a/docs/guestfs-hacking.pod +++ b/docs/guestfs-hacking.pod @@ -560,6 +560,11 @@ L<virt-get-kernel(1)> command and documentation. Gnulib is used as a portability library. A copy of gnulib is included under here. +=item F<inspection> + +Inspection. See L<guestfs(3)/INSPECTION> and +L<guestfs-inspection(8)>. + =item F<inspector> L<virt-inspector(1)>, the virtual machine image inspector. @@ -838,6 +843,7 @@ Optionally do a full release of the development branch. L<guestfs(3)>, L<guestfs-examples(3)>, +L<guestfs-inspection(8)>, L<guestfs-internals(3)>, L<guestfs-performance(1)>, L<guestfs-release-notes(1)>, diff --git a/generator/Makefile.am b/generator/Makefile.am index bcdc20d..29712c0 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -170,7 +170,7 @@ noinst_DATA = stamp-generator # Git removes empty directories, so in cases where the # generator is creating the sole file in a directory, we # have to create the directory first. -stamp-generator: generator +stamp-generator: generator $(wildcard $(top_srcdir)/inspection/*.rules) mkdir -p $(top_srcdir)/perl/lib/Sys mkdir -p $(top_srcdir)/ruby/ext/guestfs mkdir -p $(top_srcdir)/java/com/redhat/et/libguestfs diff --git a/generator/main.ml b/generator/main.ml index 0230a2f..7625852 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -212,6 +212,10 @@ Run it from the top source directory using the command output_to "customize/customize-synopsis.pod" generate_customize_synopsis_pod; output_to "customize/customize-options.pod" generate_customize_options_pod; + (* Run the rules compiler to generate inspection rules. *) + output_to "inspection/rules.c" + (Rules_compiler.compile "inspection/inspection.rules"); + (* Generate the list of files generated -- last. *) printf "generated %d lines of code\n" (get_lines_generated ()); let files = List.sort compare (get_files_generated ()) in diff --git a/inspection/Makefile.am b/inspection/Makefile.am new file mode 100644 index 0000000..9227249 --- /dev/null +++ b/inspection/Makefile.am @@ -0,0 +1,92 @@ +# libguestfs +# Copyright (C) 2015 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 $(top_srcdir)/subdir-rules.mk + +generator_built = \ + rules.c + +BUILT_SOURCES = \ + $(generator_built) + +CLEANFILES = \ + stamp-guestfs-inspection.pod \ + guestfs-inspection.8 + +# Build the inspection program. + +if INSTALL_DAEMON +sbin_PROGRAMS = guestfs-inspection +else +noinst_PROGRAMS = guestfs-inspection +endif + +SHARED_SOURCE_FILES = \ + ../daemon/cleanups.c \ + ../daemon/cleanups.h \ + ../daemon/command.c \ + ../daemon/command.h + +guestfs_inspection_SOURCES = \ + $(SHARED_SOURCE_FILES) \ + detect.c \ + facts.c \ + inspection.c \ + inspection.h \ + mount.c \ + rules.c \ + stringsbuf.c \ + stringsbuf.h + +guestfs_inspection_LDADD = \ + $(AUGEAS_LIBS) \ + $(HIVEX_LIBS) \ + $(PCRE_LIBS) \ + $(top_builddir)/gnulib/lib/.libs/libgnu.a + +guestfs_inspection_CPPFLAGS = \ + -I$(top_srcdir)/gnulib/lib \ + -I$(top_builddir)/gnulib/lib \ + -I$(top_srcdir)/daemon \ + -I$(top_builddir)/daemon \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src + +guestfs_inspection_CFLAGS = \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) \ + $(AUGEAS_CFLAGS) \ + $(HIVEX_CFLAGS) \ + $(PCRE_CFLAGS) + +# Manual pages and HTML files for the website. +if INSTALL_DAEMON +man_MANS = guestfs-inspection.8 +else +noinst_MANS = guestfs-inspection.8 +endif +noinst_DATA = $(top_builddir)/website/guestfs-inspection.8.html + +guestfs-inspection.8 $(top_builddir)/website/guestfs-inspection.8.html: stamp-guestfs-inspection.pod + +stamp-guestfs-inspection.pod: guestfs-inspection.pod + $(PODWRAPPER) \ + --section 8 \ + --man guestfs-inspection.8 \ + --html $(top_builddir)/website/guestfs-inspection.8.html \ + --license GPLv2+ \ + $< + touch $@ diff --git a/inspection/detect.c b/inspection/detect.c new file mode 100644 index 0000000..46b7c9e --- /dev/null +++ b/inspection/detect.c @@ -0,0 +1,130 @@ +/* guestfs-inspection + * Copyright (C) 2009-2015 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 <error.h> + +#include "inspection.h" + +int +get_distro_from_os_release (const char *fs, char **distro) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_os_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_product_name_from_os_release (const char *fs, char **product_name) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_distro_from_lsb_release (const char *fs, char **distro) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_lsb_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_product_name_from_lsb_release (const char *fs, char **product_name) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_oracle_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_centos_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_altlinux_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_fedora (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_rhel (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_centos (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_scientific_linux (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_redhat_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_product_name_from_release_file (const char *fs, const char *release_file, char **product_name) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} diff --git a/inspection/facts.c b/inspection/facts.c new file mode 100644 index 0000000..fc4ca8c --- /dev/null +++ b/inspection/facts.c @@ -0,0 +1,318 @@ +/* guestfs-inspection + * Copyright (C) 2009-2015 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. + */ + +/* Handle the true and false facts sets, and other helper functions. */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <inttypes.h> +#include <assert.h> +#include <error.h> +#include <errno.h> +#include <alloca.h> + +#include "gl_oset.h" +#include "gl_xoset.h" +#include "gl_array_oset.h" + +#include "inspection.h" + +/* True and false facts sets. See guestfs-inspection(8)/WRITING RULES + * to understand why these are used. + */ +static gl_oset_t true_facts = NULL; +static gl_oset_t false_facts = NULL; + +/* Used to store a single true or false fact. The only reason we know + * it's a false fact is because it would be stored in the false_facts + * set. + */ +struct fact { + char *term_name; + size_t nr_term_args; + char *term_arg[0]; +}; + +static int +compare_facts (const void *vf1, const void *vf2) +{ + size_t i; + int r; + const fact *f1 = vf1; + const fact *f2 = vf2; + + r = strcmp (f1->term_name, f2->term_name); + if (r != 0) return r; + + /* If term names are equal, they are supposed to have the same + * number of arguments. We type-checked that when reading the + * source. However, better check. + */ + assert (f1->nr_term_args == f2->nr_term_args); + + for (i = 0; i < f1->nr_term_args; ++i) { + r = strcmp (f1->term_arg[i], f2->term_arg[i]); + if (r != 0) return r; + } + + return 0; +} + +static void +free_fact (const void *vf) +{ + /* Why is the parameter const? + * See Bruno Haible's explanation here: + * https://www.mail-archive.com/bug-gnulib@gnu.org/msg08619.html + */ + fact *f = (fact *) vf; + size_t i; + + free (f->term_name); + for (i = 0; i < f->nr_term_args; ++i) + free (f->term_arg[i]); + free (f); +} + +static void init_facts (void) __attribute__((constructor)); + +static void +init_facts (void) +{ + true_facts = gl_oset_create_empty (GL_ARRAY_OSET, compare_facts, free_fact); + false_facts = gl_oset_create_empty (GL_ARRAY_OSET, compare_facts, free_fact); +} + +static void free_facts (void) __attribute__((destructor)); + +static void +free_facts (void) +{ + gl_oset_free (true_facts); + gl_oset_free (false_facts); +} + +static void +clear_set (gl_oset_t set) +{ + const void *f; + gl_oset_iterator_t iter = gl_oset_iterator (set); + + while (gl_oset_iterator_next (&iter, &f)) { + gl_oset_remove (set, (void *) f); + } + gl_oset_iterator_free (&iter); +} + +void +clear_true_facts (void) +{ + clear_set (true_facts); +} + +void +clear_false_facts (void) +{ + clear_set (false_facts); +} + +size_t +count_true_facts (void) +{ + return gl_oset_size (true_facts); +} + +/* This is just for debugging facts. */ +void +print_fact (bool is_true, fact *f, FILE *fp) +{ + size_t i; + + if (!is_true) + fputc ('!', fp); + fputs (f->term_name, fp); + if (f->nr_term_args > 0) + fputc ('(', fp); + for (i = 0; i < f->nr_term_args; ++i) { + fputc ('"', fp); + fputs (f->term_arg[i], fp); + fputc ('"', fp); + if (i+1 != f->nr_term_args) + fputs (", ", fp); + } + if (f->nr_term_args > 0) + fputc (')', fp); +} + +static void +print_set (bool is_true, gl_oset_t set) +{ + const void *vf; + gl_oset_iterator_t iter = gl_oset_iterator (set); + + while (gl_oset_iterator_next (&iter, &vf)) { + fact *f = (fact *) vf; + print_fact (is_true, f, stdout); + printf ("\n"); + } + gl_oset_iterator_free (&iter); +} + +void +print_true_facts (void) +{ + print_set (true, true_facts); +} + +void +print_false_facts (void) +{ + print_set (false, false_facts); +} + +/* Look for every string parameter of every fact we know about, and + * add all those strings to the set. + */ +void +add_all_fact_strings (gl_oset_t set) +{ + const void *vf; + fact *f; + gl_oset_iterator_t iter; + size_t i; + + iter = gl_oset_iterator (true_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + for (i = 0; i < f->nr_term_args; ++i) + gl_oset_add (set, f->term_arg[i]); + } + gl_oset_iterator_free (&iter); + + iter = gl_oset_iterator (false_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + for (i = 0; i < f->nr_term_args; ++i) + gl_oset_add (set, f->term_arg[i]); + } + gl_oset_iterator_free (&iter); +} + +/* Look for every string parameter in the specific argument position + * of the specific term name (both true and false), and add those + * strings only to the set. + */ +void +add_strings_from_facts (gl_oset_t set, const char *term_name, size_t arg_i) +{ + const void *vf; + fact *f; + gl_oset_iterator_t iter; + + iter = gl_oset_iterator (true_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + if (strcmp (f->term_name, term_name) == 0) + gl_oset_add (set, f->term_arg[arg_i]); + } + gl_oset_iterator_free (&iter); + + iter = gl_oset_iterator (false_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + if (strcmp (f->term_name, term_name) == 0) + gl_oset_add (set, f->term_arg[arg_i]); + } + gl_oset_iterator_free (&iter); +} + +/* NB: This does not make a deep copy of the strings. However before + * we add the fact to the true_facts or false_facts arrays, we do call + * deep_copy to copy the strings. + */ +fact * +create_fact (const char *term_name, size_t n, ...) +{ + fact *f; + size_t i; + va_list args; + + f = malloc (sizeof (*f) + n * sizeof (char *)); + if (f == NULL) + error (EXIT_FAILURE, errno, "malloc"); + va_start (args, term_name); + for (i = 0; i < n; ++i) { + const char *p = va_arg (args, const char *); + f->term_arg[i] = (char *) p; + } + va_end (args); + f->term_name = (char *) term_name; + f->nr_term_args = n; + return f; /* caller must free only the struct */ +} + +static fact * +deep_copy_fact (const fact *f) +{ + fact *ret; + size_t i; + + ret = malloc (sizeof (*ret) + f->nr_term_args * sizeof (char *)); + if (ret == NULL) + error (EXIT_FAILURE, errno, "malloc"); + + ret->term_name = strdup (f->term_name); + if (ret->term_name == NULL) + error (EXIT_FAILURE, errno, "strdup"); + + ret->nr_term_args = f->nr_term_args; + + for (i = 0; i < f->nr_term_args; ++i) { + ret->term_arg[i] = strdup (f->term_arg[i]); + if (ret->term_arg[i] == NULL) + error (EXIT_FAILURE, errno, "strdup"); + } + + return ret; +} + +bool +is_fact (bool is_true, const fact *f) +{ + return gl_oset_search (is_true ? true_facts : false_facts, f); +} + +bool +add_fact (bool is_true, const fact *f) +{ + fact *f2 = deep_copy_fact (f); + bool ret; + + ret = gl_oset_add (is_true ? true_facts : false_facts, f2); + + /* Didn't add it, so we must free the deep copy. */ + if (!ret) + free_fact (f2); + + return ret; +} diff --git a/inspection/guestfs-inspection.pod b/inspection/guestfs-inspection.pod new file mode 100644 index 0000000..6c4ce16 --- /dev/null +++ b/inspection/guestfs-inspection.pod @@ -0,0 +1,450 @@ +=head1 NAME + +guestfs-inspection - guestfs inspection program + +=head1 SYNOPSIS + + guestfs-inspection + +=head1 NOTE + +This man page documents the guestfs inspection program. If you want +to read about guestfs inspection then this is the wrong place. See +L<guestfs(3)/INSPECTION> instead. + +=head1 DESCRIPTION + +C<guestfs-inspection> is a standalone program that performs inspection +on the local disks, to find out what operating system(s) are +installed. It normally runs inside the libguestfs appliance, started +by L<guestfsd(8)>, when the caller uses the C<guestfs_inspect_os> API +(see L<guestfs-internals(1)> and L<guestfs(3)/guestfs_inspect_os>). +You should never need to run this program by hand. + +The program looks at all disks attached to the appliance, looking for +filesystems that might belong to operating systems. It may mount +these temporarily to examine them for Linux configuration files, +Windows Registries, and so on. It then tries to determine what +operating system(s) are installed on the disks. It is able to detect +many different Linux distributions, Windows, BSD, and others. The +currently mounted root filesystem is ignored, since when running under +libguestfs, that filesystem is part of the libguestfs appliance (this +is the main difference compared to programs like C<facter>). + +Guestfs-inpection is written in C, but most of the C is generated by a +rules compiler from a set of inspection rules written in a more +compact, declarative, Prolog-inspired language. If you want to write +or modify the rules, see L</WRITING RULES> below. + +=head1 OPTIONS + +=over 4 + +=item B<-?> + +=item B<--help> + +Display brief help. + +=item B<-v> + +=item B<--verbose> + +Enable verbose messages for debugging. + +=back + +=head1 WRITING RULES + +Inspection is performed according to a set of rules written in a +compact, declarative, Prolog-inspired language. This section explains +how this language works, so you can write your own rules to detect +other operating systems. + +The rules can be found starting in C<inspection/inspection.rules> (in +the libguestfs sources). The rules are compiled down to C and linked +into the guestfs-inspection program, together with a bit of extra C +code to provide runtime support. + +=head2 Facts + +Facts are what we try to determine about the operating system(s) we +are inspecting. They look like this: + + Filesystem("/dev/sda1") + +which means "F</dev/sda1> is a filesystem". + + File("/dev/sda1", "/etc/fstab") + +which means "there exists a file called F</etc/fstab> on the +F</dev/sda1> filesystem". + +Facts come in three flavours: true facts, false facts, and unknown +facts. False facts are written like this: + + ! File("/dev/sda1", "/etc/fstab") + +which means "either F</dev/sda1> is not a filesystem or there does not +exist a file called F</etc/fstab> on this filesystem". + +Unknown facts are facts that we don't know if they are true or false +yet. + +=head2 Rules + +Rules are used to generate more facts. A simple rule for generating +C<File> facts might look like this: + + File(fs, filename) :- + Filesystem(fs), + {{ + // some C code to mount 'fs' and check for 'filename' + }}. + +You can read this as: "For all C<fs> & C<filename>, if C<fs> is a +filesystem, and running the C code with parameters C<fs> and +C<filename> returns true, then C<File(fs, filename)> is a true fact". + +In the Prolog-inspired language, a comma (C<,>) is the AND operator. +A semicolon (C<;>) is the OR operator. C<:-> is a backwards +if-statement (the condition is on the right, the conclusion is on the +left). Also notice the dot (C<.>) which must follow each rule. + +Uppercase identifiers are facts. Lowercase identifiers are variables. +All identifiers are case-sensitive. + +Everything in C<{{ ... }}> is embedded C code. In this case the C +code returns a true/false/error indication, but embedded C code can +also do more complicated things and return strings and lists as we'll +see later. + +You can use parentheses C<(...)> for grouping expressions on the right +hand side of the C<:-> operator. + +=head2 Program evaluation + +Let's take a simple set of rules which you might use to detect a +Fedora root filesystem: + + File(fs, filename) :- + Filesystem(fs), + {{ + // some C code to mount 'fs' and check for 'filename' + }}. + + Fedora(rootfs) :- + Filesystem(rootfs), + File(rootfs, "/etc/fedora-release"). + +When evaluating this program, there are two sets of facts, the true +facts and the false facts. Let's start with the false facts set being +empty, and let's seed the true facts set with some C<Filesystem> +facts: + + true_facts = { Filesystem("/dev/sda1"), Filesystem("/dev/sda3") } + false_facts = { } // empty set + +Unknown facts are facts which don't appear in either set. + +Evaluating the program works like this: We consider each rule in turn, +and see if we can find new true or false facts from it. These new +facts are added to the true or false facts sets. After looking at +each rule in the program, as long as at least one new fact was added +to the true facts set, we go back to the start of the rules and repeat +over. We do this until we can no longer add any new true facts, and +then we're done. + +In the case of this program, we start with the C<File> rule, and we +substitute (theoretically) every possible string for C<fs> and +C<filename>. + +For example, this substitution: + + File("/dev/sda1", "/etc/fedora-release") :- + Filesystem("/dev/sda1"), + {{ // checks for file and returns false }}. + +turns out to be false (because the C code doesn't find F</etc/fstab> +in F</dev/sda1>), so that yields a new false fact: + + ! File("/dev/sda1", "/etc/fedora-release") + +But this substitution turns out to be true: + + File("/dev/sda3", "/etc/fedora-release") :- + Filesystem("/dev/sda3"), + {{ // checks for file and returns true }}. + +so that yields a new true fact: + + File("/dev/sda3", "/etc/fedora-release") + +In theory every possible string is tried, eg C<File("ardvark", "foo123654")>. +That would take literally forever to run, but luckily the rules +compiler is smarter. + +Looking now at the second rule, we try this substitution: + + Fedora("/dev/sda3") :- + Filesystem("/dev/sda3"), + File("/dev/sda3", "/etc/fedora-release"). + +which yields another new true fact: + + Fedora("/dev/sda3") + +Because we added several new true facts to the set, we go back and +repeat the whole process. But after trying all the rules for a second +time, no more true facts can be added, so now we're done. + +At the end, the set of true facts is: + + true_facts = { Filesystem("/dev/sda1"), Filesystem("/dev/sda3"), + File("/dev/sda3", "/etc/fedora-release"), + Fedora("/dev/sda3") } + +We don't care about the false facts -- they are discarded at the end +of the program. + +The summary of inspection is that F</dev/sda3> contains a Fedora root +filesystem. + +Of course real inspection is much more complicated than this, but the +same program evaluation order is followed. + +=head2 Some caveats with the language + +It's easy to look at an expression like: + + Fedora(rootfs) :- + Filesystem(rootfs), + File(rootfs, "/etc/fedora-release"). /* line 3 */ + +and think that line 3 is "calling" the "File function". This is +B<not> what is happening! Rules are not functions. Rules are +considered in isolation. Rules don't "call" other rules. Instead +when trying to find possible values that can be substituted into a +rule, we only look at the rule and the current sets of true and false +facts. + +When searching for values to subsitute, in theory the compiler would +have to look at every possible string. In practice of course it can't +and doesn't do that. Instead it looks at the current sets of true and +false facts to find strings to substitute. In the following rule: + + File(fs, filename) :- + Filesystem(fs), + {{ // C code }}. + +suitable choices for C<fs> are found by looking at any C<Filesystem> +facts in either the true or false sets. + +In some cases, this doesn't work, as in the example above where we +have no clues for the C<filename> variable. In that case the compiler +tries every string literal from every rule in the program. This can +be inefficient, but by modifying the rule slightly you can avoid this. +In the following program, only the strings F</etc/fstab> and +F</etc/fedora-release> would be tried: + + Filename("/etc/fstab"). + Filename("/etc/fedora-release"). + File(fs, filename) :- + Filesystem(fs), + Filename(filename), + {{ // C code }}. + +=head2 C expressions returning boolean + +Simple C code enclosed in C<{{ ... }}> as shown above should return a +true, false or error status only. It returns true by returning any +integer E<ge> 1. It should return C<0> to indicate false, and it +should return C<-1> to indicate an error (which stops the program and +causes inspection to fail with a user-visible error). + +Here is an example of a simple C expression returning a boolean: + + File(fs, filename) :- + Filesystem(fs), + {{ + int r; + char *relative_filename; + r = get_mount (fs, filename, &relative_filename); + if (r != 1) return r; + r = access (relative_filename, F_OK); + free (relative_filename); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR) + return 0; + perror ("access"); + return -1; + } + return 1; + }}. + +Notice that C<fs> and C<filename> are passed into the C code as local +variables. + +You can see that dealing with errors is a bit involved, because we +want to fail hard if some error like C<EIO> is thrown. + +=head2 C expressions returning strings + +C expressions can also return strings or tuples of strings. This is +useful where you want to parse the content of external files. + +The syntax for this sort of C expression is: + + (var1, var2, ...)={{ ... }} + +where C<var1>, C<var2>, etc. are outputs from the C code. + +In the following example, a lot of error checking has been omitted +for clarity: + + ProductName(fs, product_name) :- + Unix_root(fs), + Distro(fs, "RHEL"), + (product_name)={{ + int r; + char *line = NULL; + size_t n; + char *relative_filename; + r = get_mount (fs, "/etc/redhat-release", &relative_filename); + FILE *fp = fopen (relative_filename, "r"); + free (relative_filename); + getline (&line, &n, fp); + fclose (fp); + set_product_name (line); + free (line); + return 0; + }}. + +The C code calls a function C<set_product_name> (that the compiler +generates). + +The return value from the C code should be C<0> if everything was OK, +or C<-1> if there is a error (which stops the whole program). + +=head2 C expressions returning multiple results + +Finally it is possible for C code to return multiple results. + +The syntax is: + + (var1, var2, ...)*={{ ... }} + + (var1, var2, ...)?={{ ... }} + + (var1, var2, ...)+={{ ... }} + +where C<var1>, C<var2>, etc. are outputs. Unlike the previous rules, +these rules may generate zero or multiple facts from a single string +substitution. + +For example, here is how we could populate a list of C<Filesystem> +facts: + + Filesystem(fs) :- + (fs)*={{ + int i; + extern char **fs; + for (i = 0; fs[i] != NULL; ++i) { + set_fs (fs[i]); + } + return 0; + }}. + +In this case, the C code repeatedly calls a function C<set_fs> (that +the compiler generates) for each new filesystem discovered. Multiple +C<Filesystem> facts can be generated as a result of one application of +this rule. + +As with regular expressions, C<*> means we expect zero or more rows of +results, C<?> means zero or one, and C<+> means one or more. The +generated target code checks that you call the C<set_*> function the +correct number of times. + +The return value from the C code should be C<0> if everything was OK, +or C<-1> if there is a error (which stops the whole program). + +=head2 Prologue and epilogue + +If you want to insert arbitrary chunks of C code into the output, for +example to C<#include> headers, then you can insert literal code +between C<{{ ... }}> at the top and bottom of the file (ie. before +all rules and after all rules). You cannot insert these chunks +between rules. + +=head2 C code memoization + +Although currently B<only partially implemented>, in future we will +implement memoization of C code. This means that for every input, the +C code will be called only once, making it less important that C code +is efficient: no matter how many times we need to evaluate if +F</etc/fstab> exists on F</dev/sda1>, the C code would only be called +once. + +This means that impure C functions returning different results for the +same input won't work in future, so don't do that. + +=head2 Type checking + +The current language treats every value as a string. Every expression +is a boolean. One possible future enhancement is to handle other +types. There is still some minimal type checking applied: + +=over 4 + +=item * + +A fact name which appears on a right hand side of any rule must also +appear on the left hand side of a rule. This is mainly for catching +typos. + +=item * + +A fact must have the same number of arguments ("arity") each time it +appears in the source. + +=back + +=head2 Debugging + +You can debug the evaluation of inspection programs by calling +C<guestfs_set_verbose> (or setting C<$LIBGUESTFS_DEBUG=1>) before +launching the handle. + +This causes L<guestfsd(8)> to pass the I<--verbose> parameter to this +inspection program, which in turn causes the inspection program to +print information about what rules it is trying and what true/false +facts it has found. These are passed back to libguestfs and printed +on C<stderr> (or sent to the event system if you are using that). + +You can also print debug messages from C code embedded in C<{{...}}> +expressions. These are similarly sent upwards through to libguestfs +and will appear on C<stderr>. + +Remember that C memoization [to be implemented in a future version] +can cause C code to run fewer times than expected. + +=head1 EXIT STATUS + +This program returns 0 if successful, or non-zero if there was an +error. + +=head1 SEE ALSO + +L<guestfsd(8)>, +L<guestfs-hacking(1)>, +L<guestfs-internals(1)>, +L<guestfs(3)/INSPECTION>, +L<http://libguestfs.org/>. + +=head1 AUTHOR + +Richard W.M. Jones L<http://people.redhat.com/~rjones/> + +=head1 COPYRIGHT + +Copyright (C) 2009-2015 Red Hat Inc. diff --git a/inspection/inspection.c b/inspection/inspection.c new file mode 100644 index 0000000..f74e028 --- /dev/null +++ b/inspection/inspection.c @@ -0,0 +1,106 @@ +/* guestfs-inspection + * Copyright (C) 2009-2015 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 <string.h> +#include <inttypes.h> +#include <getopt.h> +#include <unistd.h> + +#include "inspection.h" + +int verbose = 0; +const char *sysroot = NULL; +const size_t sysroot_len = 0; + +/* Required by the gnulib 'error' module. */ +const char *program_name = "guestfs-inspection"; + +static void __attribute__((noreturn)) +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, "Try `%s --help' for more information.\n", + program_name); + else { + printf ("%s: guestfs inspection\n" + "Copyright (C) 2009-2015 Red Hat Inc.\n" + "Usage:\n" + "Options:\n" + " --help Display brief help\n" + " -v|--verbose Verbose messages\n" + " -V|--version Display version and exit\n" + "For more information, see the manpage %s(8).\n", + program_name, program_name); + } + exit (status); +} + +int +main (int argc, char *argv[]) +{ + enum { HELP_OPTION = CHAR_MAX + 1 }; + + static const char *options = "vV"; + static const struct option long_options[] = { + { "help", 0, 0, HELP_OPTION }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { 0, 0, 0, 0 } + }; + int c; + int option_index; + + for (;;) { + c = getopt_long (argc, argv, options, long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 0: /* options which are long only */ + fprintf (stderr, "%s: unknown long option: %s (%d)\n", + program_name, + long_options[option_index].name, option_index); + exit (EXIT_FAILURE); + + case 'v': + verbose++; + break; + + case 'V': + printf ("%s %s\n", program_name, PACKAGE_VERSION_FULL); + exit (EXIT_SUCCESS); + + case HELP_OPTION: + usage (EXIT_SUCCESS); + + default: + usage (EXIT_FAILURE); + } + } + + /* Run the rules. */ + rules (); + + /* Print the true facts. XXX Output XXX */ + print_true_facts (); + + exit (EXIT_SUCCESS); +} diff --git a/inspection/inspection.h b/inspection/inspection.h new file mode 100644 index 0000000..c0feda0 --- /dev/null +++ b/inspection/inspection.h @@ -0,0 +1,79 @@ +/* guestfs-inspection + * Copyright (C) 2009-2015 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. + */ + +#ifndef GUESTFS_INSPECTION_H +#define GUESTFS_INSPECTION_H + +#include <stdbool.h> + +#include "gl_oset.h" + +#include "cleanups.h" +#include "command.h" + +extern int verbose; + +/* detect.c - used by rules */ +extern int get_distro_from_os_release (const char *fs, char **distro); +extern int get_version_from_os_release (const char *fs, char **major, char **minor); +extern int get_product_name_from_os_release (const char *fs, char **product_name); +extern int get_distro_from_lsb_release (const char *fs, char **distro); +extern int get_version_from_lsb_release (const char *fs, char **major, char **minor); +extern int get_product_name_from_lsb_release (const char *fs, char **product_name); +extern int get_version_from_oracle_release (const char *fs, char **major, char **minor); +extern int get_version_from_centos_release (const char *fs, char **major, char **minor); +extern int get_version_from_altlinux_release (const char *fs, char **major, char **minor); +extern int match_redhat_release_fedora (const char *fs); +extern int match_redhat_release_rhel (const char *fs); +extern int match_redhat_release_centos (const char *fs); +extern int match_redhat_release_scientific_linux (const char *fs); +extern int get_version_from_redhat_release (const char *fs, char **major, char **minor); +extern int get_product_name_from_release_file (const char *fs, const char *release_file, char **product_name); + +/* facts.c */ +typedef struct fact fact; +extern void clear_true_facts (void); +extern void clear_false_facts (void); +extern size_t count_true_facts (void); +extern void print_fact (bool is_true, fact *f, FILE *fp); +extern void print_true_facts (void); +extern void print_false_facts (void); +extern void add_all_fact_strings (gl_oset_t set); +extern void add_strings_from_facts (gl_oset_t set, const char *term_name, size_t arg_i); +extern fact *create_fact (const char *term_name, size_t n, ...); +extern bool is_fact (bool is_true, const fact *); +extern bool add_fact (bool is_true, const fact *); + +/* rules.c - generated code */ +extern const char *all_strings[]; +extern void rules (void); + +/* mount.c - used by rules */ +extern char **get_all_block_devices (void); +extern char **get_all_partitions (void); +extern char **get_all_mddevs (void); +extern char **get_all_lvs (void); +extern char **get_all_ldmvols (void); +extern char **get_all_ldmparts (void); +extern char **get_all_btrfs_subvolumes (const char *fs); +extern char *get_vfs_type (const char *fs); +extern int get_partition_mbr_id (const char *fs); +extern int is_mountable (const char *fs); +extern int get_mount (const char *fs, const char *filename, char **relative_filename); + +#endif /* GUESTFS_INSPECTION_H */ diff --git a/inspection/inspection.rules b/inspection/inspection.rules new file mode 100644 index 0000000..646d28f --- /dev/null +++ b/inspection/inspection.rules @@ -0,0 +1,478 @@ +/* Libguestfs inspection rules -*- prolog -*- + * Copyright (C) 2009-2016 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. + */ + +/* To understand what's going on here, it's recommended that you read + * guestfs-inspection(8) (inspection/guestfs-inspection.pod) first. + */ + +{{ +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +}} + +/* Whole block devices. */ +BlockDevice(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_block_devices (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Partitions. */ +Partition(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_partitions (); + if (devs == NULL) return -1; + /* Ignore partitions with type byte 0x42 (RHBZ#887520). */ + for (size_t i = 0; devs[i] != NULL; ++i) { + int r = get_partition_mbr_id (devs[i]); + if (r == -1) return -1; + if (r != 0x42) set_dev (devs[i]); + } + return 0; + }}. + +/* LVM2 logical volumes. */ +LV(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_lvs (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* /dev/md* devices. */ +MDDev(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_mddevs (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Windows LDM voumes. */ +LDMVol(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmvols (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Windows LDM partitions. */ +LDMPart(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmparts (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Device(dev) is just a group name for block devices, partitions etc. */ +Device(dev) :- + BlockDevice(dev); Partition(dev); LV(dev); + MDDev(dev); LDMVol(dev); LDMPart(dev). + +/* Map a filesystem to its VFS type (from blkid). */ +VFSType(fs, vfs_type) :- + Device(fs), + (vfs_type)?={{ + CLEANUP_FREE char *vfs_type = get_vfs_type (fs); + if (vfs_type != NULL) set_vfs_type (vfs_type); + return 0; + }}. + +/* A device contains a mountable filesystem (not swap, empty, etc). */ +Mountable(fs) :- + Device(fs), + {{ return is_mountable (fs); }}. + +/* Where a filesystem is btrfs and mountable, get the subvolumes. */ +BtrfsSubvolume(subvol) :- + Device(fs), + Mountable(fs), + VFSType(fs, "btrfs"), + (subvol)*={{ + size_t i; + CLEANUP_FREE_STRING_LIST char **paths = get_all_btrfs_subvolumes (fs); + if (paths == NULL) return -1; + for (i = 0; paths[i] != NULL; ++i) { + CLEANUP_FREE char *subvol; + if (asprintf (&subvol, "btrfsvol:%s/%s", fs, paths[i]) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + set_subvol (subvol); + } + return 0; + }}. +VFSType(subvol, "btrfs") :- BtrfsSubvolume(subvol). + +/* Ignore all *_member types. In libblkid these are returned + * for things which are members of some RAID or LVM set, most + * importantly "LVM2_member" which is a PV. Also ignore + * crypto_LUKS (LUKS encrypted partition). + */ +ContainerDevice(dev) :- + VFSType(dev, vfs_type), + {{ return STRSUFFIX (vfs_type, "_member"); }}. +ContainerDevice(dev) :- + VFSType(dev, "crypto_LUKS"). + +/* Ignore all swap devices. */ +SwapDevice(dev) :- VFSType(dev, "swap"). + +/* This rule generates one Filesystem(fs) fact per mountable + * filesystem found in the appliance. A filesystem could be + * a device, partition, LV, btrfs subvolume, etc. + */ +Filesystem(fs) :- + !ContainerDevice(fs), + !SwapDevice(fs), + (Device(fs), Mountable(fs); BtrfsSubvolume(fs)). + +/* File(fs, filename) is true if filename is a regular file in fs. + * It also follows symlinks. + */ +File(fs, filename) :- + Filesystem(fs), + {{ + int r; + CLEANUP_FREE char *relative_filename = NULL; + struct stat statbuf; + + if (filename[0] != '/') return 0; + r = get_mount (fs, filename, &relative_filename); + if (r != 1) return r; + r = stat (relative_filename, &statbuf); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP) + return 0; + perror (relative_filename); + return -1; + } + return S_ISREG (statbuf.st_mode); + }}. + +/* Directory(fs, dirname) is true if dirname is a directory in fs. + * It also follows symlinks. + */ +Directory(fs, dirname) :- + Filesystem(fs), + {{ + int r; + CLEANUP_FREE char *relative_dirname = NULL; + struct stat statbuf; + + if (dirname[0] != '/') return 0; + r = get_mount (fs, dirname, &relative_dirname); + if (r != 1) return r; + r = stat (relative_dirname, &statbuf); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP) + return 0; + perror (relative_dirname); + return -1; + } + return S_ISDIR (statbuf.st_mode); + }}. + +/* Symlink(fs, filename) is true if filename is a symlink. */ +Symlink(fs, filename) :- + Filesystem(fs), + {{ + int r; + CLEANUP_FREE char *relative_filename = NULL; + struct stat statbuf; + + if (filename[0] != '/') return 0; + r = get_mount (fs, filename, &relative_filename); + if (r != 1) return r; + r = stat (relative_filename, &statbuf); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP) + return 0; + perror (relative_filename); + return -1; + } + return S_ISLNK (statbuf.st_mode); + }}. + +/* grub or grub2 /boot */ +GrubBoot(fs) :- + Filesystem(fs), + File(fs, "/grub/menu.lst"), + File(fs, "/grub/grub.conf"), + File(fs, "/grub2/grub.cfg"). + +/* FreeBSD root. */ +FreeBSDRoot(fs) :- + Filesystem(fs), + Directory(fs, "/bin"), + Directory(fs, "/etc"), + File(fs, "/etc/freebsd-update.conf"), + File(fs, "/etc/fstab"). + +/* NetBSD root. */ +NetBSDRoot(fs) :- + Filesystem(fs), + Directory(fs, "/bin"), + Directory(fs, "/etc"), + File(fs, "/netbsd"), + File(fs, "/etc/fstab"), + File(fs, "/etc/release"). + +/* OpenBSD root. */ +OpenBSDRoot(fs) :- + Filesystem(fs), + Directory(fs, "/bin"), + Directory(fs, "/etc"), + File(fs, "/bsd"), + File(fs, "/etc/fstab"), + File(fs, "/etc/motd"). + +/* Hurd root. */ +HurdRoot(fs) :- + Filesystem(fs), + File(fs, "/hurd/console"), + File(fs, "/hurd/hello"), + File(fs, "/hurd/null"). + +/* Minix root. */ +MinixRoot(fs) :- + Filesystem(fs), + File(fs, "/service/vm"), + File(fs, "/etc/fstab"), + File(fs, "/etc/version"). + +/* Linux root (any distro). */ +LinuxRoot(fs) :- + Filesystem(fs), + Directory(fs, "/etc"), + File(fs, "/etc/fstab"), + (Directory(fs, "/bin"); Symlink(fs, "/bin")), + !FreeBSDRoot(fs), + !NetBSDRoot(fs), + !OpenBSDRoot(fs), + !HurdRoot(fs), + !MinixRoot(fs). + +/* Is it Linux using /etc/os-release? (Linux systemd). */ +LinuxRootWithOSRelease(fs) :- + LinuxRoot(fs), + File(fs, "/etc/os-release"). + +Distro(fs, distro) :- + LinuxRootWithOSRelease(fs), + (distro)?={{ + int r; + CLEANUP_FREE char *distro = NULL; + if ((r = get_distro_from_os_release (fs, &distro)) <= 0) + return r; + set_distro (distro); + return 0; + }}. + +Version(fs, major, minor) :- + LinuxRootWithOSRelease(fs), + (major, minor)?={{ + int r; + CLEANUP_FREE char *major = NULL, *minor = NULL; + if ((r = get_version_from_os_release (fs, &major, &minor)) <= 0) + return r; + set_major_minor (major, minor); + return 0; + }}. + +ProductName(fs, product_name) :- + LinuxRootWithOSRelease(fs), + (product_name)?={{ + int r; + CLEANUP_FREE char *product_name = NULL; + if ((r = get_product_name_from_os_release (fs, &product_name)) <= 0) + return r; + set_product_name (product_name); + return 0; + }}. + +/* Is it Linux using /etc/lsb-release? (Linux Standards Base). */ +LinuxRootWithLSBRelease(fs) :- + LinuxRoot(fs), + File(fs, "/etc/lsb-release"), + !LinuxRootWithOSRelease(fs). /* prefer /etc/os-release */ + +Distro(fs, distro) :- + LinuxRootWithLSBRelease(fs), + (distro)?={{ + int r; + CLEANUP_FREE char *distro = NULL; + if ((r = get_distro_from_lsb_release (fs, &distro)) <= 0) + return r; + set_distro (distro); + return 0; + }}. + +Version(fs, major, minor) :- + LinuxRootWithLSBRelease(fs), + (major, minor)?={{ + int r; + CLEANUP_FREE char *major = NULL, *minor = NULL; + if ((r = get_version_from_lsb_release (fs, &major, &minor)) <= 0) + return r; + set_major_minor (major, minor); + return 0; + }}. + +ProductName(fs, product_name) :- + LinuxRootWithLSBRelease(fs), + (product_name)?={{ + int r; + CLEANUP_FREE char *product_name = NULL; + if ((r = get_product_name_from_lsb_release (fs, &product_name)) <= 0) + return r; + set_product_name (product_name); + return 0; + }}. + +/* Fall back on /etc/*-release files to determine the distro, version and + * product name. + */ +LinuxRootWithReleaseFile(fs, "/etc/oracle-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + File(fs, "/etc/oracle-release"). +Distro(fs, "oracle-linux") :- + LinuxRootWithReleaseFile(fs, "/etc/oracle-release"). +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/oracle-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_oracle_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +LinuxRootWithReleaseFile(fs, "/etc/centos-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + File(fs, "/etc/centos-release"). +Distro(fs, "centos") :- + LinuxRootWithReleaseFile(fs, "/etc/centos-release"). +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/centos-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_centos_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +LinuxRootWithReleaseFile(fs, "/etc/altlinux-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + File(fs, "/etc/altlinux-release"). +Distro(fs, "altlinux") :- + LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"). +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_altlinux_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +/* Handle the fallback case of /etc/redhat-release. */ +LinuxRootWithReleaseFile(fs, "/etc/redhat-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + !LinuxRootWithReleaseFile(fs, "/etc/oracle-release"), + !LinuxRootWithReleaseFile(fs, "/etc/centos-release"), + !LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"), + File(fs, "/etc/redhat-release"). +Distro(fs, "fedora") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_fedora (fs); }}. +Distro(fs, "rhel") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_rhel (fs); }}. +Distro(fs, "centos") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_centos (fs); }}. +Distro(fs, "scientificlinux") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_scientific_linux (fs); }}. +Distro(fs, "redhat-based") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + !Distro(fs, "fedora"), + !Distro(fs, "rhel"), + !Distro(fs, "centos"), + !Distro(fs, "scientificlinux"). + +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_redhat_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +/* Get the product name from a generic Linux release file. */ +ProductName(fs, product_name) :- + LinuxRootWithReleaseFile(fs, release_file), + (product_name)={{ + CLEANUP_FREE char *product_name = NULL; + if (get_product_name_from_release_file (fs, release_file, + &product_name) == -1) + return -1; + set_product_name (product_name); + return 0; + }}. + +/* XXX debian, arch-linux etc release files */ + +/* XXX Linux architecture, fstab, hostname */ + + + +/* XXX CoreOS etc. */ + + + + + + + + +/* +Has_fstab(rootfs) :- + Unix_root(fs), + File(rootfs, "/etc/fstab"). +Mount(rootfs, dev, mountpoint) :- + Has_fstab(rootfs), + (dev, mountpoint)*={{ + // code to return (dev, mountpoint) pairs from /etc/fstab + }}. +*/ diff --git a/inspection/mount.c b/inspection/mount.c new file mode 100644 index 0000000..2186bbd --- /dev/null +++ b/inspection/mount.c @@ -0,0 +1,591 @@ +/* guestfs-inspection + * Copyright (C) 2009-2015 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 <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mount.h> +#include <errno.h> +#include <error.h> + +#include "c-ctype.h" + +#include "guestfs-internal-all.h" + +#include "inspection.h" +#include "stringsbuf.h" + +/* If root device is an ext2 filesystem, this is the major and minor. + * This is so we can ignore this device from the point of view of the + * user, eg. in guestfs_list_devices and many other places. + */ +static dev_t root_device = 0; + +/* A temporary directory where we place all the mountpoints. */ +static char mountpoints[] = "/tmp/mp.XXXXXX"; + +static void init_mount (void) __attribute__((constructor)); + +static void +init_mount (void) +{ + struct stat statbuf; + + if (stat ("/", &statbuf) == 0) + root_device = statbuf.st_dev; + + if (mkdtemp (mountpoints) == NULL) + perror ("mkdtemp"); +} + +static void free_mount (void) __attribute__((destructor)); + +static void +free_mount (void) +{ + DIR *dir; + struct dirent *d; + + /* Unmount all mountpoints in the temporary directory, then + * delete those directories and the parent. + */ + dir = opendir (mountpoints); + if (!dir) { + perror (mountpoints); + return; + } + + for (;;) { + errno = 0; + d = readdir (dir); + if (!d) break; + + if (d->d_name[0] != '.') { + CLEANUP_FREE char *mp; + + if (asprintf (&mp, "%s/%s", mountpoints, d->d_name) == -1) { + perror ("asprintf"); + continue; + } + + if (umount2 (mp, MNT_DETACH) == -1) /* lazy umount */ + perror (mp); + + if (rmdir (mp) == -1) + perror (mp); + } + } + + /* Check readdir didn't fail */ + if (errno != 0) { + perror ("readdir"); + return; + } + + /* Close the directory handle */ + if (closedir (dir) == -1) { + perror ("closedir"); + return; + } + + if (rmdir (mountpoints) == -1) + perror (mountpoints); +} + +/* Return true iff device is the root device (and therefore should be + * ignored from the point of view of user calls). + */ +static int +is_root_device_stat (struct stat *statbuf) +{ + if (statbuf->st_rdev == root_device) return 1; + return 0; +} + +static int +is_root_device (const char *device) +{ + struct stat statbuf; + + if (stat (device, &statbuf) == -1) { + perror (device); + return 0; + } + + return is_root_device_stat (&statbuf); +} + +typedef int (*block_dev_func_t) (const char *dev, struct stringsbuf *r); + +/* Execute a given function for each discovered block device */ +static char ** +foreach_block_device (block_dev_func_t func) +{ + DECLARE_STRINGSBUF (r); + DIR *dir; + struct dirent *d; + char dev_path[256]; + int fd; + bool err = false; + + dir = opendir ("/sys/block"); + if (!dir) { + perror ("opendir: /sys/block"); + return NULL; + } + + for (;;) { + errno = 0; + d = readdir (dir); + if (!d) break; + + if (STREQLEN (d->d_name, "sd", 2) || + STREQLEN (d->d_name, "hd", 2) || + STREQLEN (d->d_name, "ubd", 3) || + STREQLEN (d->d_name, "vd", 2) || + STREQLEN (d->d_name, "sr", 2)) { + snprintf (dev_path, sizeof dev_path, "/dev/%s", d->d_name); + + /* Ignore the root device. */ + if (is_root_device (dev_path)) + continue; + + /* RHBZ#514505: Some versions of qemu <= 0.10 add a + * CD-ROM device even though we didn't request it. Try to + * detect this by seeing if the device contains media. + */ + fd = open (dev_path, O_RDONLY|O_CLOEXEC); + if (fd == -1) { + perror (dev_path); + continue; + } + close (fd); + + /* Call the map function for this device */ + if ((*func)(d->d_name, &r) != 0) { + err = true; + break; + } + } + } + + /* Check readdir didn't fail */ + if (errno != 0) { + perror ("readdir: /sys/block"); + free_stringslen (r.argv, r.size); + closedir (dir); + return NULL; + } + + /* Close the directory handle */ + if (closedir (dir) == -1) { + perror ("closedir: /sys/block"); + free_stringslen (r.argv, r.size); + return NULL; + } + + /* Free the result list on error */ + if (err) { + free_stringslen (r.argv, r.size); + return NULL; + } + + /* Sort the devices. */ + if (r.size > 0) + sort_device_names (r.argv, r.size); + + /* NULL terminate the list */ + end_stringsbuf (&r); + + return r.argv; +} + +/* Add a device to the list of devices */ +static int +add_device (const char *device, struct stringsbuf *r) +{ + char dev_path[256]; + snprintf (dev_path, sizeof dev_path, "/dev/%s", device); + + add_string (r, dev_path); + + return 0; +} + +char ** +get_all_block_devices (void) +{ + return foreach_block_device (add_device); +} + +static int +add_partitions (const char *device, struct stringsbuf *r) +{ + char devdir[256]; + + /* Open the device's directory under /sys/block */ + snprintf (devdir, sizeof devdir, "/sys/block/%s", device); + + DIR *dir = opendir (devdir); + if (!dir) { + perror (devdir); + free_stringslen (r->argv, r->size); + return -1; + } + + /* Look in /sys/block/<device>/ for entries starting with <device> + * e.g. /sys/block/sda/sda1 + */ + errno = 0; + struct dirent *d; + while ((d = readdir (dir)) != NULL) { + if (STREQLEN (d->d_name, device, strlen (device))) { + char part[256]; + snprintf (part, sizeof part, "/dev/%s", d->d_name); + + add_string (r, part); + } + } + + /* Check if readdir failed */ + if (0 != errno) { + perror (devdir); + free_stringslen (r->argv, r->size); + closedir (dir); + return -1; + } + + /* Close the directory handle */ + if (closedir (dir) == -1) { + perror (device); + free_stringslen (r->argv, r->size); + return -1; + } + + return 0; +} + +char ** +get_all_partitions (void) +{ + return foreach_block_device (add_partitions); +} + +char ** +get_all_mddevs (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_lvs (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_ldmvols (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_ldmparts (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_btrfs_subvolumes (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +static char * +get_blkid_tag (const char *device, const char *tag) +{ + char *out; + CLEANUP_FREE char *err = NULL; + int r; + size_t len; + + r = commandr (&out, &err, + "blkid", + /* Adding -c option kills all caching, even on RHEL 5. */ + "-c", "/dev/null", + "-o", "value", "-s", tag, device, NULL); + if (r != 0 && r != 2) { + if (r >= 0) + fprintf (stderr, "%s: %s (blkid returned %d)\n", device, err, r); + else + fprintf (stderr, "%s: %s\n", device, err); + free (out); + return NULL; + } + + if (r == 2) { /* means UUID etc not found */ + free (out); + out = strdup (""); + if (out == NULL) + perror ("strdup"); + return out; + } + + /* Trim trailing \n if present. */ + len = strlen (out); + if (len > 0 && out[len-1] == '\n') + out[len-1] = '\0'; + + return out; /* caller frees */ +} + +char * +get_vfs_type (const char *fs) +{ + return get_blkid_tag (fs, "TYPE"); +} + +/* Test if sfdisk is recent enough to have --part-type, to be used instead + * of --print-id and --change-id. + */ +static int +test_sfdisk_has_part_type (void) +{ + static int tested = -1; + int r; + CLEANUP_FREE char *out = NULL, *err = NULL; + + if (tested != -1) + return tested; + + r = command (&out, &err, "sfdisk", "--help", NULL); + if (r == -1) { + fprintf (stderr, "%s: %s\n", "sfdisk --help", err); + return -1; + } + + tested = strstr (out, "--part-type") != NULL; + return tested; +} + +static char * +part_to_dev (const char *part) +{ + int err = 1; + size_t n = strlen (part); + char *r; + + while (n >= 1 && c_isdigit (part[n-1])) { + err = 0; + n--; + } + + if (err) { + fprintf (stderr, "device name is not a partition\n"); + return NULL; + } + + r = strndup (part, n); + if (r == NULL) { + perror ("strdup"); + return NULL; + } + + return r; +} + +static int +part_to_partnum (const char *part) +{ + int err = 1; + size_t n = strlen (part); + int r; + + while (n >= 1 && c_isdigit (part[n-1])) { + err = 0; + n--; + } + + if (err) { + fprintf (stderr, "device name is not a partition\n"); + return -1; + } + + if (sscanf (&part[n], "%d", &r) != 1) { + fprintf (stderr, "could not parse number\n"); + return -1; + } + + return r; +} + +int +get_partition_mbr_id (const char *fs) +{ + CLEANUP_FREE char *device; + int partnum; + char partnum_str[16]; + const char *param + test_sfdisk_has_part_type () ? "--part-type" : "--print-id"; + CLEANUP_FREE char *out = NULL, *err = NULL; + int r; + unsigned id; + + /* Get the block device and partition number from the filesystem + * string. + */ + device = part_to_dev (fs); + if (device == NULL) + return -1; + partnum = part_to_partnum (fs); + if (partnum == -1) + return -1; + snprintf (partnum_str, sizeof partnum_str, "%d", partnum); + + r = command (&out, &err, "sfdisk", param, device, partnum_str, NULL); + if (r == -1) { + fprintf (stderr, "sfdisk %s: %s\n", param, err); + return -1; + } + + /* It's printed in hex ... */ + if (sscanf (out, "%x", &id) != 1) { + fprintf (stderr, "sfdisk --print-id: cannot parse output: %s\n", out); + return -1; + } + + return id; +} + +/* When mounting filesystems, we place them in temporary directories + * under 'mountpoints'. We name the temporary directory after the + * device name, but since device names contain '/' characters, we have + * to mangle the name. + */ +static char * +get_mount_name (const char *fs) +{ + char *ret; + size_t i; + + if (asprintf (&ret, "%s/%s", mountpoints, fs) == -1) { + perror ("asprintf"); + return NULL; + } + + for (i = strlen (mountpoints) + 1; i < strlen (ret); ++i) { + if (ret[i] == '/') + ret[i] = '_'; + } + + return ret; /* caller frees */ +} + +int +is_mountable (const char *fs) +{ + CLEANUP_FREE char *mp = get_mount_name (fs); + struct stat statbuf; + int r; + CLEANUP_FREE char *err = NULL; + + if (stat (mp, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)) + return 1; /* mountable, and mounted already */ + + /* Try to create the mountpoint. */ + if (mkdir (mp, 0700) == -1) { + perror (mp); + return -1; + } + + /* Try to mount the filesystem. */ + r = command (NULL, &err, + "mount", "-o", "ro", fs, mp, NULL); + if (r == -1) { + fprintf (stderr, "mount: %s: %s\n", fs, err); + + /* Now hack things for the *BSDs. */ + /* FreeBSD fs is a variant of ufs called ufs2 ... */ + free (err); err = NULL; + r = command (NULL, &err, + "mount", "-o", "ro,ufstype=ufs2", fs, mp, NULL); + if (r == -1) { + fprintf (stderr, "mount [ufs2]: %s: %s\n", fs, err); + + /* while NetBSD and OpenBSD use another variant labeled 44bsd */ + free (err); err = NULL; + r = command (NULL, &err, + "mount", "-o", "ro,ufstype=44bsd", fs, mp, NULL); + if (r == -1) { + fprintf (stderr, "mount [44bsd]: %s: %s\n", fs, err); + + /* Mount failed, so remove the mountpoint. */ + rmdir (mp); + return 0; + } + } + } + + /* Mount succeeded. */ + return 1; +} + +int +get_mount (const char *fs, const char *filename, char **relative_filename) +{ + CLEANUP_FREE char *mp = NULL; + int r; + + if (filename[0] != '/') { + fprintf (stderr, "get_mount: filename is not an absolute path: %s\n", + filename); + return -1; + } + + r = is_mountable (fs); + if (r == -1) + return -1; + if (r == 0) { + fprintf (stderr, "get_mount: called on non-mountable filesystem: %s\n", + fs); + return -1; + } + + mp = get_mount_name (fs); + + /* Construct the filename relative to the mountpoint. */ + if (asprintf (relative_filename, "%s%s", mp, filename) == -1) { + perror ("asprintf"); + return -1; + } + + return 0; +} diff --git a/inspection/stringsbuf.c b/inspection/stringsbuf.c new file mode 100644 index 0000000..338134e --- /dev/null +++ b/inspection/stringsbuf.c @@ -0,0 +1,254 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 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 <string.h> +#include <errno.h> +#include <error.h> +#include <assert.h> + +#include "guestfs-internal-all.h" + +#include "stringsbuf.h" + +void +add_string_nodup (struct stringsbuf *sb, char *str) +{ + char **new_argv; + + if (sb->size >= sb->alloc) { + sb->alloc += 64; + new_argv = realloc (sb->argv, sb->alloc * sizeof (char *)); + if (new_argv == NULL) + error (EXIT_FAILURE, errno, "realloc"); + sb->argv = new_argv; + } + + sb->argv[sb->size] = str; + sb->size++; +} + +void +add_string (struct stringsbuf *sb, const char *str) +{ + char *new_str = NULL; + + if (str) { + new_str = strdup (str); + if (new_str == NULL) + error (EXIT_FAILURE, errno, "strdup"); + } + + add_string_nodup (sb, new_str); +} + +void +add_sprintf (struct stringsbuf *sb, const char *fs, ...) +{ + va_list args; + char *str; + int r; + + va_start (args, fs); + r = vasprintf (&str, fs, args); + va_end (args); + if (r == -1) + error (EXIT_FAILURE, errno, "vasprintf"); + + add_string_nodup (sb, str); +} + +void +end_stringsbuf (struct stringsbuf *sb) +{ + add_string_nodup (sb, NULL); +} + +void +free_stringsbuf (struct stringsbuf *sb) +{ + if (sb->argv != NULL) + free_stringslen (sb->argv, sb->size); +} + +size_t +count_strings (char *const *argv) +{ + size_t argc; + + for (argc = 0; argv[argc] != NULL; ++argc) + ; + return argc; +} + +static int +compare (const void *vp1, const void *vp2) +{ + char * const *p1 = (char * const *) vp1; + char * const *p2 = (char * const *) vp2; + return strcmp (*p1, *p2); +} + +void +sort_strings (char **argv, size_t len) +{ + qsort (argv, len, sizeof (char *), compare); +} + +void +free_strings (char **argv) +{ + size_t argc; + + if (!argv) + return; + + for (argc = 0; argv[argc] != NULL; ++argc) + free (argv[argc]); + free (argv); +} + +void +free_stringslen (char **argv, size_t len) +{ + size_t i; + + if (!argv) + return; + + for (i = 0; i < len; ++i) + free (argv[i]); + free (argv); +} + +/* Compare device names (including partition numbers if present). + * https://rwmj.wordpress.com/2011/01/09/how-are-linux-drives-named-beyond-drive-26-devsdz/ + */ +int +compare_device_names (const char *a, const char *b) +{ + size_t alen, blen; + int r; + int a_partnum, b_partnum; + + /* 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; + + /* Get device name part, that is, just 'a', 'ab' etc. */ + alen = strcspn (a, "0123456789"); + blen = strcspn (b, "0123456789"); + + /* If device name part is longer, it is always greater, eg. + * "/dev/sdz" < "/dev/sdaa". + */ + if (alen != blen) + return alen - blen; + + /* Device name parts are the same length, so do a regular compare. */ + r = strncmp (a, b, alen); + if (r != 0) + return r; + + /* Compare partitions numbers. */ + a += alen; + b += alen; + + /* If no partition numbers, bail -- the devices are the same. This + * can happen in one peculiar case: where you have a mix of devices + * with different interfaces (eg. /dev/sda and /dev/vda). + * (RHBZ#858128). + */ + if (!*a && !*b) + return 0; + + r = sscanf (a, "%d", &a_partnum); + assert (r == 1); + r = sscanf (b, "%d", &b_partnum); + assert (r == 1); + + return a_partnum - b_partnum; +} + +static int +compare_device_names_vp (const void *vp1, const void *vp2) +{ + char * const *p1 = (char * const *) vp1; + char * const *p2 = (char * const *) vp2; + return compare_device_names (*p1, *p2); +} + +void +sort_device_names (char **argv, size_t len) +{ + qsort (argv, len, sizeof (char *), compare_device_names_vp); +} + +char * +concat_strings (char *const *argv) +{ + return join_strings ("", argv); +} + +char * +join_strings (const char *separator, char *const *argv) +{ + size_t i, len, seplen, rlen; + char *r; + + seplen = strlen (separator); + + len = 0; + for (i = 0; argv[i] != NULL; ++i) { + if (i > 0) + len += seplen; + len += strlen (argv[i]); + } + len++; /* for final \0 */ + + r = malloc (len); + if (r == NULL) + error (EXIT_FAILURE, errno, "malloc"); + + rlen = 0; + for (i = 0; argv[i] != NULL; ++i) { + if (i > 0) { + memcpy (&r[rlen], separator, seplen); + rlen += seplen; + } + len = strlen (argv[i]); + memcpy (&r[rlen], argv[i], len); + rlen += len; + } + r[rlen] = '\0'; + + return r; +} diff --git a/inspection/stringsbuf.h b/inspection/stringsbuf.h new file mode 100644 index 0000000..4a26ffb --- /dev/null +++ b/inspection/stringsbuf.h @@ -0,0 +1,56 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 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. + */ + +#ifndef GUESTFS_INSPECTION_STRINGSBUF_H +#define GUESTFS_INSPECTION_STRINGSBUF_H + +/* Growable strings buffer. */ +struct stringsbuf { + char **argv; + size_t size; + size_t alloc; +}; +#define DECLARE_STRINGSBUF(v) \ + struct stringsbuf (v) = { .argv = NULL, .size = 0, .alloc = 0 } + +/* Append a string to the strings buffer. + * + * add_string_nodup: don't copy the string. + * add_string: copy the string. + * end_stringsbuf: NULL-terminate the buffer. + */ +extern void add_string_nodup (struct stringsbuf *sb, char *str); +extern void add_string (struct stringsbuf *sb, const char *str); +extern void add_sprintf (struct stringsbuf *sb, const char *fs, ...) + __attribute__((format (printf,2,3))); +extern void end_stringsbuf (struct stringsbuf *sb); +extern void free_stringsbuf (struct stringsbuf *sb); + +extern size_t count_strings (char *const *argv); +extern void sort_strings (char **argv, size_t len); +extern void free_strings (char **argv); +extern void free_stringslen (char **argv, size_t len); + +extern void sort_device_names (char **argv, size_t len); +extern int compare_device_names (const char *a, const char *b); + +/* Concatenate strings, optionally with a separator string between each. */ +extern char *concat_strings (char *const *argv); +extern char *join_strings (const char *separator, char *const *argv); + +#endif /* GUESTFS_INSPECTION_STRINGSBUF_H */ diff --git a/po/POTFILES b/po/POTFILES index aca174d..b6332f6 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -258,6 +258,11 @@ gobject/src/struct-version.c gobject/src/struct-xattr.c gobject/src/struct-xfsinfo.c gobject/src/tristate.c +inspection/detect.c +inspection/facts.c +inspection/inspection.c +inspection/mount.c +inspection/stringsbuf.c inspector/inspector.c java/com_redhat_et_libguestfs_GuestFS.c lua/lua-guestfs.c diff --git a/src/guestfs.pod b/src/guestfs.pod index a4d7d57..dfa8fc1 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -3505,6 +3505,7 @@ Other libguestfs topics: L<guestfs-building(1)>, L<guestfs-faq(1)>, L<guestfs-hacking(1)>, +L<guestfs-inspection(8)>, L<guestfs-internals(1)>, L<guestfs-performance(1)>, L<guestfs-release-notes(1)>, -- 2.5.0
Richard W.M. Jones
2016-Jan-21 15:48 UTC
[Libguestfs] [PATCH v3 5/6] inspection: Add unit tests of the rules compiler.
These are unit tests of basic features of the rules compiler, such as evaluation of expressions and embedding C code. --- .gitignore | 4 ++ generator/main.ml | 6 ++- inspection/Makefile.am | 35 +++++++++++++- inspection/test-harness.c | 51 ++++++++++++++++++++ inspection/test1.rules | 80 ++++++++++++++++++++++++++++++++ inspection/test2.rules | 116 ++++++++++++++++++++++++++++++++++++++++++++++ po/POTFILES | 3 ++ 7 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 inspection/test-harness.c create mode 100644 inspection/test1.rules create mode 100644 inspection/test2.rules diff --git a/.gitignore b/.gitignore index 0106ec3..8188318 100644 --- a/.gitignore +++ b/.gitignore @@ -255,6 +255,10 @@ Makefile.in /inspection/guestfs-inspection.8 /inspection/rules.c /inspection/stamp-guestfs-inspection.pod +/inspection/test1 +/inspection/test1.c +/inspection/test2 +/inspection/test2.c /inspector/actual-*.xml /inspector/stamp-virt-inspector.pod /inspector/test-xmllint.sh diff --git a/generator/main.ml b/generator/main.ml index 7625852..de325c6 100644 --- a/generator/main.ml +++ b/generator/main.ml @@ -212,7 +212,11 @@ Run it from the top source directory using the command output_to "customize/customize-synopsis.pod" generate_customize_synopsis_pod; output_to "customize/customize-options.pod" generate_customize_options_pod; - (* Run the rules compiler to generate inspection rules. *) + (* Run the rules compiler to generate test cases and inspection rules. *) + output_to "inspection/test1.c" + (Rules_compiler.compile "inspection/test1.rules"); + output_to "inspection/test2.c" + (Rules_compiler.compile "inspection/test2.rules"); output_to "inspection/rules.c" (Rules_compiler.compile "inspection/inspection.rules"); diff --git a/inspection/Makefile.am b/inspection/Makefile.am index 9227249..c1be08a 100644 --- a/inspection/Makefile.am +++ b/inspection/Makefile.am @@ -18,7 +18,9 @@ include $(top_srcdir)/subdir-rules.mk generator_built = \ - rules.c + rules.c \ + test1.c \ + test2.c BUILT_SOURCES = \ $(generator_built) @@ -90,3 +92,34 @@ stamp-guestfs-inspection.pod: guestfs-inspection.pod --license GPLv2+ \ $< touch $@ + +# Tests. + +TESTS = test1 test2 +check_PROGRAMS = $(TESTS) + +test1_SOURCES = \ + $(SHARED_SOURCE_FILES) \ + facts.c \ + inspection.h \ + stringsbuf.c \ + stringsbuf.h \ + test-harness.c \ + test1.c + +test1_LDADD = $(guestfs_inspection_LDADD) +test1_CPPFLAGS = $(guestfs_inspection_CPPFLAGS) +test1_CFLAGS = $(guestfs_inspection_CFLAGS) + +test2_SOURCES = \ + $(SHARED_SOURCE_FILES) \ + facts.c \ + inspection.h \ + stringsbuf.c \ + stringsbuf.h \ + test-harness.c \ + test2.c + +test2_LDADD = $(guestfs_inspection_LDADD) +test2_CPPFLAGS = $(guestfs_inspection_CPPFLAGS) +test2_CFLAGS = $(guestfs_inspection_CFLAGS) diff --git a/inspection/test-harness.c b/inspection/test-harness.c new file mode 100644 index 0000000..cfe39df --- /dev/null +++ b/inspection/test-harness.c @@ -0,0 +1,51 @@ +/* guestfs-inspection tests + * Copyright (C) 2009-2015 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 <string.h> +#include <assert.h> + +#include "inspection.h" + +int verbose = 1; + +int +main (int argc, char *argv[]) +{ + /* Run the rules. */ + rules (); + + /* For debugging. */ + print_true_facts (); + + /* The tests should contain a TestOK rule which verifies that all + * true and false facts are what we expect. So we just need to + * check that TestOK is true and we're done. + */ + CLEANUP_FREE fact *f = create_fact ("TestOK", NULL); + if (!is_fact (true, f)) { + fprintf (stderr, "%s: test failed, see debugging information above\n", + argv[0]); + exit (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); +} diff --git a/inspection/test1.rules b/inspection/test1.rules new file mode 100644 index 0000000..416ccd2 --- /dev/null +++ b/inspection/test1.rules @@ -0,0 +1,80 @@ +/* Libguestfs inspection rules unit test -*- prolog -*- + * Copyright (C) 2009-2015 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. + */ + +Filesystem(fs) :- + (fs)*={{ + const char *fses[] = { "/dev/sda1", "/dev/sda2", "/dev/sda3", NULL }; + size_t i; + for (i = 0; fses[i] != NULL; ++i) + set_fs (fses[i]); + return 0; + }}. + +File("/dev/sda1", "/etc/fstab"). +File("/dev/sda1", "/etc/debian_version"). +File("/dev/sda2", "/etc/fstab"). +File("/dev/sda2", "/etc/redhat-release"). +File("/dev/sda2", "/etc/fedora-release"). +File("/dev/sda3", "/etc/fstab"). +File("/dev/sda3", "/etc/fedora-release") :- false. +File("/dev/sda3", "/etc/redhat-release"). + +Symlink("/dev/sda1", "/bin"). + +Directory("/dev/sda1", "/lib"). +Directory("/dev/sda1", "/etc"). +Directory("/dev/sda2", "/bin"). +Directory("/dev/sda2", "/etc"). +Directory("/dev/sda2", "/lib"). +Directory("/dev/sda3", "/bin"). +Directory("/dev/sda3", "/etc"). +Directory("/dev/sda3", "/lib"). + +UnixRoot(fs) :- + Filesystem(fs), + (Directory(fs, "/bin"); Symlink(fs, "/bin")), + (File(fs, "/etc/fstab"); Symlink(fs, "/etc/fstab")), + (Directory(fs, "/lib"); Symlink(fs, "/lib")). + +Distro(rootfs, "RHEL") :- + UnixRoot(rootfs), + File(rootfs, "/etc/redhat-release"), + ! File(rootfs, "/etc/fedora-release"). + +Distro(rootfs, "Fedora") :- + UnixRoot(rootfs), + File(rootfs, "/etc/fedora-release"). + +Distro(rootfs, "Debian") :- + UnixRoot(rootfs), + File(rootfs, "/etc/debian_version"). + +ProductName(rootfs, "Fedora", product_name) :- + Filesystem(rootfs), + Distro(rootfs, "Fedora"), + (product_name)={{ + set_product_name ("Fedora release 23 (Twenty Three)"); + return 0; + }}. + +/* This rule is checked by the test harness. */ +TestOK :- + Distro("/dev/sda1", "Debian"), + Distro("/dev/sda2", "Fedora"), + Distro("/dev/sda3", "RHEL"), + ProductName("/dev/sda2", "Fedora", "Fedora release 23 (Twenty Three)"). diff --git a/inspection/test2.rules b/inspection/test2.rules new file mode 100644 index 0000000..09bc062 --- /dev/null +++ b/inspection/test2.rules @@ -0,0 +1,116 @@ +/* Libguestfs inspection rules unit test -*- prolog -*- + * Copyright (C) 2009-2015 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. + */ + +/* Test false, true expressions. */ +TestTrue1. +TestTrue2 :- true. +TestFalse :- false. + +/* Basic boolean expressions. */ +TestAnd1 :- true, true. +TestAnd2 :- true, false. +TestAnd3 :- false, false. +TestAnd4 :- false, true. +TestOr1 :- true; true. +TestOr2 :- true; false. +TestOr3 :- false; false. +TestOr4 :- false; true. +TestNot1 :- !TestTrue1. +TestNot2 :- !TestFalse. + +/* Precedence of AND and OR operators. */ +TestPrec :- true; true, false. /* should be parsed as true; (true, false) */ + +/* Parentheses. */ +TestParen1 :- (true, true); false. +TestParen2 :- false; (true, true). +TestParen3 :- (true; false), true. +TestParen4 :- true, (true; false). + +/* Cartesian product. */ +TestCartesian(x,y) :- true. + +/* Boolean code. */ +TestCTrue :- {{ return 1; }}. +TestCFalse :- {{ return 0; }}. + +/* Assignment code. */ +TestCAssign1(x) :- (x)={{ set_x ("123"); return 0; }}. +TestCAssign2(x,y) :- (x,y)={{ set_x_y ("123", "456"); return 0; }}. +TestCAssign3(x,y,z) :- (x,y,z)={{ set_x_y_z ("123", "456", "789"); return 0; }}. + +/* Assignment code for lists. */ +TestCListAssign1(x) :- + (x)*={{ /* no assignments */ return 0; }}. +TestCListAssign2(x) :- + (x)*={{ set_x ("123"); return 0; }}. +TestCListAssign3(x) :- + (x)*={{ set_x ("123"); set_x ("456"); return 0; }}. +TestCListAssign4(x,y) :- + (x,y)*={{ /* no assignments */ return 0; }}. +TestCListAssign5(x,y) :- + (x,y)*={{ set_x_y ("123", "456"); return 0; }}. +TestCListAssign6(x,y) :- + (x,y)*={{ set_x_y ("123", "456"); set_x_y ("456", "789"); return 0; }}. + +/* These assignments only differ in a small amount of checking code. */ +TestCListAssign7(x,y) :- + (x,y)?={{ /* no assignments */ return 0; }}. +TestCListAssign8(x,y) :- + (x,y)?={{ set_x_y ("123", "456"); return 0; }}. +TestCListAssign9(x,y) :- + (x,y)+={{ set_x_y ("123", "456"); return 0; }}. +TestCListAssign10(x,y) :- + (x,y)+={{ set_x_y ("123", "456"); set_x_y ("456", "789"); return 0; }}. + +/* Check no clashes in generated code with commonly used variables. */ +TestCAssignClash(i,j,fact,verbose,args,env) :- + (i,j,fact,verbose,args,env)={{ + set_i_j_fact_verbose_args_env ("1", "2", "3", "4", "5", "6"); + return 0; + }}. + +/* This rule is checked by the test harness. */ +TestOK :- + TestTrue1, TestTrue2, !TestFalse, + TestAnd1, !TestAnd2, !TestAnd3, !TestAnd4, + TestOr1, TestOr2, !TestOr3, TestOr4, + !TestNot1, TestNot2, + TestPrec, + TestParen1, TestParen2, TestParen3, TestParen4, + TestCartesian("123", "123"), + TestCartesian("123", "456"), + TestCartesian("123", "789"), + TestCartesian("456", "123"), /* etc etc */ + TestCTrue, !TestCFalse, + TestCAssign1("123"), TestCAssign2("123", "456"), + TestCAssign3("123", "456", "789"), + /* no assignments to TestCListAssign1 */ + TestCListAssign2("123"), + TestCListAssign3("123"), + TestCListAssign3("456"), + /* no assignments to TestCListAssign4 */ + TestCListAssign5("123", "456"), + TestCListAssign6("123", "456"), + TestCListAssign6("456", "789"), + /* no assignments to TestCListAssign7 */ + TestCListAssign8("123", "456"), + TestCListAssign9("123", "456"), + TestCListAssign10("123", "456"), + TestCListAssign10("456", "789"), + TestCAssignClash("1", "2", "3", "4", "5", "6"). diff --git a/po/POTFILES b/po/POTFILES index b6332f6..afdc75f 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -263,6 +263,9 @@ inspection/facts.c inspection/inspection.c inspection/mount.c inspection/stringsbuf.c +inspection/test-harness.c +inspection/test1.c +inspection/test2.c inspector/inspector.c java/com_redhat_et_libguestfs_GuestFS.c lua/lua-guestfs.c -- 2.5.0
Richard W.M. Jones
2016-Jan-21 15:48 UTC
[Libguestfs] [PATCH v3 6/6] DISABLE NON-PARTITION GUESTS TEMPORARILY
--- inspection/inspection.rules | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/inspection/inspection.rules b/inspection/inspection.rules index 646d28f..40c7400 100644 --- a/inspection/inspection.rules +++ b/inspection/inspection.rules @@ -50,40 +50,48 @@ Partition(dev) :- }}. /* LVM2 logical volumes. */ +/* LV(dev) :- (dev)*={{ CLEANUP_FREE_STRING_LIST char **devs = get_all_lvs (); if (devs == NULL) return -1; for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); return 0; - }}. + }}. */ +LV(dev) :- false. /* /dev/md* devices. */ +/* MDDev(dev) :- (dev)*={{ CLEANUP_FREE_STRING_LIST char **devs = get_all_mddevs (); if (devs == NULL) return -1; for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); return 0; - }}. + }}. */ +MDDev(dev) :- false. /* Windows LDM voumes. */ +/* LDMVol(dev) :- (dev)*={{ CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmvols (); if (devs == NULL) return -1; for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); return 0; - }}. + }}. */ +LDMVol(dev) :- false. /* Windows LDM partitions. */ +/* LDMPart(dev) :- (dev)*={{ CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmparts (); if (devs == NULL) return -1; for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); return 0; - }}. + }}. */ +LDMPart(dev) :- false. /* Device(dev) is just a group name for block devices, partitions etc. */ Device(dev) :- @@ -105,6 +113,7 @@ Mountable(fs) :- {{ return is_mountable (fs); }}. /* Where a filesystem is btrfs and mountable, get the subvolumes. */ +/* BtrfsSubvolume(subvol) :- Device(fs), Mountable(fs), @@ -123,7 +132,8 @@ BtrfsSubvolume(subvol) :- } return 0; }}. -VFSType(subvol, "btrfs") :- BtrfsSubvolume(subvol). +VFSType(subvol, "btrfs") :- BtrfsSubvolume(subvol). */ +BtrfsSubvolume(subvol) :- false. /* Ignore all *_member types. In libblkid these are returned * for things which are members of some RAID or LVM set, most -- 2.5.0
Pino Toscano
2016-Jan-22 09:54 UTC
Re: [Libguestfs] [PATCH v3 1/6] daemon: Rename daemon/command.c -> daemon/sh.c.
On Thursday 21 January 2016 15:48:11 Richard W.M. Jones wrote:> Simply a file rename, no other change. > --- > daemon/Makefile.am | 2 +- > daemon/command.c | 319 ----------------------------------------------------- > daemon/sh.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > po/POTFILES | 2 +- > 4 files changed, 321 insertions(+), 321 deletions(-) > delete mode 100644 daemon/command.c > create mode 100644 daemon/sh.cEasy enough, LGTM. -- Pino Toscano
Pino Toscano
2016-Jan-22 10:03 UTC
Re: [Libguestfs] [PATCH v3 2/6] daemon: Split out command() functions and CLEANUP_* macros into separate files.
On Thursday 21 January 2016 15:48:12 Richard W.M. Jones wrote:> Allows more sharing between the daemon and the inspection program. > --- > daemon/Makefile.am | 2 + > daemon/cleanups.c | 80 ++++++++++ > daemon/cleanups.h | 47 ++++++ > daemon/command.c | 436 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > daemon/command.h | 41 +++++ > daemon/daemon.h | 47 +----- > daemon/guestfsd.c | 392 ----------------------------------------------- > po/POTFILES | 2 + > 8 files changed, 611 insertions(+), 436 deletions(-) > create mode 100644 daemon/cleanups.c > create mode 100644 daemon/cleanups.h > create mode 100644 daemon/command.c > create mode 100644 daemon/command.hMostly LGTM, just a couple of notes below.> +#ifndef GUESTFSD_CLEANUPS_H > +#define GUESTFSD_CLEANUPS_H > + > +/* Use by the CLEANUP_* macros. */Can you please use the same comment as in guestfs-internal-frontend.h?> diff --git a/daemon/command.c b/daemon/command.c > new file mode 100644 > index 0000000..3e757aa > --- /dev/null > +++ b/daemon/command.c > @@ -0,0 +1,436 @@ > +/* libguestfs - the guestfsd daemon > + * Copyright (C) 2009-2015 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 <string.h> > +#include <unistd.h> > +#include <fcntl.h> > +#include <sys/types.h> > +#include <sys/stat.h> > +#include <sys/wait.h> > +#include <error.h> > +#include <errno.h> > + > +#include "ignore-value.h" > + > +#include "command.h" > +#include "cleanups.h" > + > +extern int verbose; > + > +extern const char *sysroot; > +extern size_t sysroot_len; > + > +#ifndef MAX > +# define MAX(a,b) ((a)>(b)?(a):(b)) > +#endifThis is available in guestfs-internal-all.h -- shouldn't this new command.c include it as well? Should be useful also for the separate inspector tool, I guess. Thanks, -- Pino Toscano
Possibly Parallel Threads
- [PATCH v3 2/6] daemon: Split out command() functions and CLEANUP_* macros into separate files.
- [PATCH v7 13/13] daemon: Link guestfsd with libutils.
- [PATCH] daemon: improve internal commandrvf
- [PATCH v3 0/6] [FOR COMMENTS ONLY] Rework inspection.
- [PATCH] daemon: Use CLEANUP_* functions to avoid an explicit free in stub functions.