Richard W.M. Jones
2016-May-25 16:37 UTC
[Libguestfs] [PATCH 0/4] qemu: Use sqlite to store qemu detection data.
Patches 1 & 2 were posted previously here: https://www.redhat.com/archives/libguestfs/2016-May/msg00134.html Patch 3 is a hack so I can test this using my own version of qemu (the `-L ?' stuff is not upstream). Patch 4 is where the real action takes place: Replace the caching of qemu features in blob-like files with a sqlite database. Probably the best way to approach this patch is to start with the schema (src/qemu-schema.sql) and the sqlite C API documentation (https://www.sqlite.org/cintro.html). Rich.
Richard W.M. Jones
2016-May-25 16:37 UTC
[Libguestfs] [PATCH 1/4] qemu: Add a function to test if -bios feature is supported.
This has to read the data dirs from qemu using the new 'qemu -L ?' flag added in qemu 2.7. --- src/guestfs-internal.h | 1 + src/qemu.c | 90 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 03f1034..2df29bf 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -922,6 +922,7 @@ struct qemu_data; extern struct qemu_data *guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version); extern int guestfs_int_qemu_supports (guestfs_h *g, const struct qemu_data *, const char *option); extern int guestfs_int_qemu_supports_device (guestfs_h *g, const struct qemu_data *, const char *device_name); +extern int guestfs_int_qemu_supports_bios (guestfs_h *g, const struct qemu_data *, const char *bios_name); extern int guestfs_int_qemu_supports_virtio_scsi (guestfs_h *g, struct qemu_data *, const struct version *qemu_version); extern char *guestfs_int_drive_source_qemu_param (guestfs_h *g, const struct drive_source *src); extern bool guestfs_int_discard_possible (guestfs_h *g, struct drive *drv, const struct version *qemu_version); diff --git a/src/qemu.c b/src/qemu.c index 11bf5cf..73ab8b4 100644 --- a/src/qemu.c +++ b/src/qemu.c @@ -48,11 +48,12 @@ COMPILE_REGEXP (re_major_minor, "(\\d+)\\.(\\d+)", 0) struct qemu_data { - char *qemu_help; /* Output of qemu -help. */ - char *qemu_devices; /* Output of qemu -device ? */ + char *qemu_help; /* Output of qemu -help. */ + char *qemu_devices; /* Output of qemu -device ? */ + char *qemu_datadirs; /* Output of qemu -L ? (NULL if not supported) */ - int virtio_scsi; /* See function - guestfs_int_qemu_supports_virtio_scsi */ + int virtio_scsi; /* See function + guestfs_int_qemu_supports_virtio_scsi */ }; static int test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version); @@ -68,8 +69,9 @@ static void read_all (guestfs_h *g, void *retv, const char *buf, size_t len); /** * Test qemu binary (or wrapper) runs, and do C<qemu -help> so we know - * the version of qemu what options this qemu supports, and - * C<qemu -device ?> so we know what devices are available. + * the version of qemu what options this qemu supports, + * C<qemu -device ?> so we know what devices are available, + * and C<qemu -L ?> to list data directories. * * The version number of qemu (from the C<-help> output) is saved in * C<&qemu_version>. @@ -83,7 +85,8 @@ guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version) struct qemu_data *data; struct stat statbuf; CLEANUP_FREE char *cachedir = NULL, *qemu_stat_filename = NULL, - *qemu_help_filename = NULL, *qemu_devices_filename = NULL; + *qemu_help_filename = NULL, *qemu_devices_filename = NULL, + *qemu_datadirs_filename = NULL; FILE *fp; int generation; uint64_t prev_size, prev_mtime; @@ -100,6 +103,7 @@ guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version) qemu_stat_filename = safe_asprintf (g, "%s/qemu.stat", cachedir); qemu_help_filename = safe_asprintf (g, "%s/qemu.help", cachedir); qemu_devices_filename = safe_asprintf (g, "%s/qemu.devices", cachedir); + qemu_datadirs_filename = safe_asprintf (g, "%s/qemu.datadirs", cachedir); /* Did we previously test the same version of qemu? */ debug (g, "checking for previously cached test results of %s, in %s", @@ -122,7 +126,8 @@ guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version) * and qemu -devices ? output. */ if (access (qemu_help_filename, R_OK) == -1 || - access (qemu_devices_filename, R_OK) == -1) + access (qemu_devices_filename, R_OK) == -1 || + access (qemu_datadirs_filename, R_OK) == -1) goto do_test; debug (g, "loading previously cached test results"); @@ -143,6 +148,12 @@ guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version) return NULL; } + if (guestfs_int_read_whole_file (g, qemu_datadirs_filename, + &data->qemu_datadirs, NULL) == -1) { + guestfs_int_free_qemu_data (data); + return NULL; + } + return data; } @@ -183,6 +194,19 @@ guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version) if (fclose (fp) == -1) goto devices_error; + fp = fopen (qemu_datadirs_filename, "w"); + if (fp == NULL) { + datadirs_error: + perrorf (g, "%s", qemu_datadirs_filename); + if (fp != NULL) fclose (fp); + guestfs_int_free_qemu_data (data); + return NULL; + } + if (fprintf (fp, "%s", data->qemu_datadirs) == -1) + goto datadirs_error; + if (fclose (fp) == -1) + goto datadirs_error; + /* Write the qemu.stat file last so that its presence indicates that * the qemu.help and qemu.devices files ought to exist. */ @@ -214,6 +238,7 @@ test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version) { CLEANUP_CMD_CLOSE struct command *cmd1 = guestfs_int_new_command (g); CLEANUP_CMD_CLOSE struct command *cmd2 = guestfs_int_new_command (g); + CLEANUP_CMD_CLOSE struct command *cmd3 = guestfs_int_new_command (g); int r; guestfs_int_cmd_add_arg (cmd1, g->hv); @@ -247,6 +272,28 @@ test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version) if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) goto error; + /* qemu -L ? only supported in qemu >= 2.7 */ + if (guestfs_int_version_ge (qemu_version, 2, 7, 0)) { + guestfs_int_cmd_add_arg (cmd3, g->hv); + guestfs_int_cmd_add_arg (cmd3, "-display"); + guestfs_int_cmd_add_arg (cmd3, "none"); + guestfs_int_cmd_add_arg (cmd3, "-machine"); + guestfs_int_cmd_add_arg (cmd3, +#ifdef MACHINE_TYPE + MACHINE_TYPE "," +#endif + "accel=kvm:tcg"); + guestfs_int_cmd_add_arg (cmd3, "-L"); + guestfs_int_cmd_add_arg (cmd3, "?"); + guestfs_int_cmd_set_stdout_callback (cmd3, read_all, &data->qemu_datadirs, + CMD_STDOUT_FLAG_WHOLE_BUFFER); + r = guestfs_int_cmd_run (cmd3); + if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) + goto error; + } + else + data->qemu_datadirs = safe_strdup (g, ""); + return 0; error: @@ -321,6 +368,32 @@ guestfs_int_qemu_supports_device (guestfs_h *g, return strstr (data->qemu_devices, device_name) != NULL; } +/** + * Test if a named BIOS is supported by qemu. + */ +int +guestfs_int_qemu_supports_bios (guestfs_h *g, + const struct qemu_data *data, + const char *bios_name) +{ + CLEANUP_FREE_STRING_LIST char **datadirs; + size_t i; + + datadirs = guestfs_int_split_string ('\n', data->qemu_datadirs); + if (datadirs == NULL) + return 0; /* ignore errors, return false which is safe */ + + for (i = 0; datadirs[i] != NULL; ++i) { + CLEANUP_FREE char *path; + + path = safe_asprintf (g, "%s/%s", datadirs[i], bios_name); + if (access (path, R_OK) == 0) + return 1; + } + + return 0; +} + static int old_or_broken_virtio_scsi (const struct version *qemu_version) { @@ -706,6 +779,7 @@ guestfs_int_free_qemu_data (struct qemu_data *data) if (data) { free (data->qemu_help); free (data->qemu_devices); + free (data->qemu_datadirs); free (data); } } -- 2.7.4
Richard W.M. Jones
2016-May-25 16:37 UTC
[Libguestfs] [PATCH 2/4] launch: Use -bios bios-fast.bin where supported.
This is a cut-down version of the SeaBIOS configuration optimized for booting a Linux kernel with the -kernel option on x86. It was added in qemu 2.7. Use it if available. --- src/launch-direct.c | 6 ++++++ src/launch-libvirt.c | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/launch-direct.c b/src/launch-direct.c index 20f6471..332118e 100644 --- a/src/launch-direct.c +++ b/src/launch-direct.c @@ -436,6 +436,12 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) ADD_CMDLINE ("-initrd"); ADD_CMDLINE (initrd); + if (guestfs_int_qemu_supports_bios (g, data->qemu_data, "bios-fast.bin")) { + /* Use the fast variant of SeaBIOS. */ + ADD_CMDLINE ("-bios"); + ADD_CMDLINE ("bios-fast.bin"); + } + /* Add a random number generator (backend for virtio-rng). This * isn't strictly necessary but means we won't need to hang around * when needing entropy. diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c index 05ab6a6..9f76afa 100644 --- a/src/launch-libvirt.c +++ b/src/launch-libvirt.c @@ -1159,6 +1159,15 @@ construct_libvirt_xml_boot (guestfs_h *g, } end_element (); } } +#if defined(__i386__) || defined(__x86_64__) + /* XXX libvirt should provide a way to detect if bios-fast.bin exists */ + else if (guestfs_int_version_ge (¶ms->data->qemu_version, 2, 7, 0)) { + /* Use the fast variant of SeaBIOS. */ + start_element ("loader") { + string ("bios-fast.bin"); + } end_element (); + } +#endif start_element ("kernel") { string (params->kernel); -- 2.7.4
Richard W.M. Jones
2016-May-25 16:37 UTC
[Libguestfs] [PATCH 3/4] QEMU: HACK VERSION TO TEST -L ?
--- src/qemu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qemu.c b/src/qemu.c index 73ab8b4..f8ba5d2 100644 --- a/src/qemu.c +++ b/src/qemu.c @@ -273,7 +273,7 @@ test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version) goto error; /* qemu -L ? only supported in qemu >= 2.7 */ - if (guestfs_int_version_ge (qemu_version, 2, 7, 0)) { + if (guestfs_int_version_ge (qemu_version, 2, 6 /* XXX 7 */, 0)) { guestfs_int_cmd_add_arg (cmd3, g->hv); guestfs_int_cmd_add_arg (cmd3, "-display"); guestfs_int_cmd_add_arg (cmd3, "none"); -- 2.7.4
Richard W.M. Jones
2016-May-25 16:37 UTC
[Libguestfs] [PATCH 4/4] qemu: Use sqlite to store qemu detection data.
Instead of saving qemu detection data in separate qemu.stat, qemu.help, etc files, use a sqlite database for this data. This adds quite a lot of code, but most of the complexity is because we now handle concurrency correctly (the previous code probably suffered from corruption bugs if multiple threads tried to update the qemu.* files at the same time). Also the use of a database allows us to do more advanced things: - We can store qemu information for multiple versions of qemu. - We can store the data in a structured way, instead of using "file blobs". sqlite is fast. On the hot path it only takes 0.4ms to open and read the cached test results from the database. This obviously adds sqlite3 as a new dependency of the library. --- .gitignore | 1 + docs/guestfs-building.pod | 4 + m4/guestfs_libraries.m4 | 3 + src/Makefile.am | 28 ++- src/launch-direct.c | 3 +- src/qemu-schema.sql | 70 ++++++ src/qemu.c | 626 +++++++++++++++++++++++++++++++++------------- 7 files changed, 558 insertions(+), 177 deletions(-) create mode 100644 src/qemu-schema.sql diff --git a/.gitignore b/.gitignore index 285b28d..4b55a3d 100644 --- a/.gitignore +++ b/.gitignore @@ -465,6 +465,7 @@ Makefile.in /src/libguestfs.syms /src/.libs/libguestfs.so /src/libvirt-is-version +/src/qemu-schema.c /src/stamp-guestfs.pod /src/structs-cleanup.c /src/structs-compare.c diff --git a/docs/guestfs-building.pod b/docs/guestfs-building.pod index f42d25f..3e48cc9 100644 --- a/docs/guestfs-building.pod +++ b/docs/guestfs-building.pod @@ -139,6 +139,10 @@ I<Required>. I<Required>. +=item sqlite3 + +I<Required>. + =item genisoimage I<Required>. diff --git a/m4/guestfs_libraries.m4 b/m4/guestfs_libraries.m4 index e845ea9..4b6db95 100644 --- a/m4/guestfs_libraries.m4 +++ b/m4/guestfs_libraries.m4 @@ -234,6 +234,9 @@ PKG_CHECK_MODULES([PCRE], [libpcre]) dnl Check for Augeas >= 1.0.0 (required). PKG_CHECK_MODULES([AUGEAS],[augeas >= 1.0.0]) +dnl Check for sqlite3 (required) +PKG_CHECK_MODULES([SQLITE], [sqlite3]) + dnl libmagic (highly recommended) AC_CHECK_LIB([magic],[magic_file],[ AC_CHECK_HEADER([magic.h],[ diff --git a/src/Makefile.am b/src/Makefile.am index d659f8d..3dc3592 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,7 +19,7 @@ include $(top_srcdir)/subdir-rules.mk noinst_PROGRAMS -CLEANFILES = stamp-guestfs.pod +CLEANFILES = qemu-schema.c stamp-guestfs.pod generator_built = \ guestfs_protocol.x \ @@ -54,14 +54,16 @@ BUILT_SOURCES = \ $(generator_built) \ guestfs_protocol.c \ guestfs_protocol.h \ - errnostring-gperf.c + errnostring-gperf.c \ + qemu-schema.c EXTRA_DIST = \ $(BUILT_SOURCES) \ MAX_PROC_NR \ libguestfs.3 \ libguestfs.pc.in \ - guestfs.pod + guestfs.pod \ + qemu-schema.sql include_HEADERS = guestfs.h @@ -126,6 +128,7 @@ libguestfs_la_SOURCES = \ private-data.c \ proto.c \ qemu.c \ + qemu-schema.c \ stringsbuf.c \ structs-compare.c \ structs-copy.c \ @@ -148,6 +151,7 @@ libguestfs_la_CFLAGS = \ $(WARN_CFLAGS) $(WERROR_CFLAGS) \ $(GCC_VISIBILITY_HIDDEN) \ $(PCRE_CFLAGS) \ + $(SQLITE_CFLAGS) \ $(LIBVIRT_CFLAGS) \ $(LIBXML2_CFLAGS) \ $(YAJL_CFLAGS) @@ -157,6 +161,7 @@ libguestfs_la_LIBADD = \ libprotocol.la \ libutils.la \ $(PCRE_LIBS) $(MAGIC_LIBS) \ + $(SQLITE_LIBS) \ $(LIBVIRT_LIBS) $(LIBXML2_LIBS) \ $(SELINUX_LIBS) \ $(YAJL_LIBS) \ @@ -228,6 +233,23 @@ guestfs_protocol.h: guestfs_protocol.x mv $@-t $@ endif +# qemu-schema.c is built from qemu-schema.sql +qemu-schema.c: qemu-schema.sql + rm -f $@-t $@ + $(AWK) ' \ + BEGIN { \ + OFS = ""; \ + print "/* Generated from qemu-schema.sql */"; \ + print "const char *guestfs_int_qemu_schema ="; \ + } \ + /^--/ { next } \ + /^$$/ { next } \ + { print "\"", $$0, "\\n\"" } \ + END { print ";" } \ + ' < $< > $@-t + mv $@-t $@ + chmod -w $@ + # libutils.la contains code outside libguestfs which is also # included in tools and bindings. libutils_la_SOURCES = \ diff --git a/src/launch-direct.c b/src/launch-direct.c index 332118e..6bef940 100644 --- a/src/launch-direct.c +++ b/src/launch-direct.c @@ -579,8 +579,7 @@ launch_direct (guestfs_h *g, void *datav, const char *arg) ADD_CMDLINE ("stdio"); if (g->verbose && - guestfs_int_qemu_supports_device (g, data->qemu_data, - "Serial Graphics Adapter")) { + guestfs_int_qemu_supports_device (g, data->qemu_data, "sga")) { /* Use sgabios instead of vgabios. This means we'll see BIOS * messages on the serial port, and also works around this bug * in qemu 1.1.0: diff --git a/src/qemu-schema.sql b/src/qemu-schema.sql new file mode 100644 index 0000000..1332b47 --- /dev/null +++ b/src/qemu-schema.sql @@ -0,0 +1,70 @@ +-- -*- mode: sql; sql-product: sqlite; -*- +-- libguestfs +-- Copyright (C) 2016 Red Hat Inc. +-- +-- This library is free software; you can redistribute it and/or +-- modify it under the terms of the GNU Lesser General Public +-- License as published by the Free Software Foundation; either +-- version 2 of the License, or (at your option) any later version. +-- +-- This library 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 +-- Lesser General Public License for more details. +-- +-- You should have received a copy of the GNU Lesser General Public +-- License along with this library; if not, write to the Free Software +-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +-- This is the sqlite3 schema used to cache the qemu detection data. +-- See qemu.c for further details. +-- +-- Notes: +-- +-- (1) If schema changes you MUST increment SCHEMA_GENERATION in +-- qemu.c. That's so we don't have to deal with updating an existing +-- database to the new schema. +-- +-- (2) ONLY use 'create table if not exists' or 'create unique index +-- if not exists' commands. That is so it is safe for multiple +-- processes to run this SQL at the same time. You must not use other +-- statements such as inserts etc. + +-- The list of qemu binaries that we have detected. +create table if not exists + qemu_binary ( + id integer not null primary key, + size integer not null, -- size of binary + mtime integer not null, -- mtime of binary + path string not null, -- path to binary (for info only) + help string not null, -- qemu -help output + major integer not null, -- qemu version + minor integer not null, + release integer not null + ); + +create unique index if not exists + qemu_binary_size_mtime on qemu_binary (size, mtime); + +-- The 'qemu -device ?' output. +-- +-- There may be multiple lines per qemu binary. +create table if not exists + qemu_devices ( + qemuid integer not null, + device string not null, + foreign key (qemuid) references qemu_binary (id) + ); + +-- The 'qemu -L ?' output (datadirs). +-- +-- There may be multiple lines per qemu binary, or no lines for +-- old versions of qemu which did not support -L ?. The lines are +-- ordered by the id. +create table if not exists + qemu_datadirs ( + id integer not null primary key, + qemuid integer not null, + datadir string not null, + foreign key (qemuid) references qemu_binary (id) + ); diff --git a/src/qemu.c b/src/qemu.c index f8ba5d2..a829869 100644 --- a/src/qemu.c +++ b/src/qemu.c @@ -39,39 +39,69 @@ #include <pcre.h> +#include <sqlite3.h> + #include "ignore-value.h" #include "guestfs.h" #include "guestfs-internal.h" #include "guestfs_protocol.h" +#ifdef HAVE_ATTRIBUTE_CLEANUP +#define CLEANUP_SQLITE3_FREE __attribute__((cleanup(cleanup_sqlite3_free))) +#define CLEANUP_SQLITE3_FINALIZE __attribute__((cleanup(cleanup_sqlite3_finalize))) + +static void +cleanup_sqlite3_free (void *vpp) +{ + char *p = * (char **) vpp; + sqlite3_free (p); +} + +static void +cleanup_sqlite3_finalize (void *ptr) +{ + sqlite3_stmt *stmt = * (sqlite3_stmt **) ptr; + sqlite3_finalize (stmt); +} + +#else /* !HAVE_ATTRIBUTE_CLEANUP */ +#define CLEANUP_SQLITE3_FREE +#define CLEANUP_SQLITE3_FINALIZE +#endif + COMPILE_REGEXP (re_major_minor, "(\\d+)\\.(\\d+)", 0) struct qemu_data { - char *qemu_help; /* Output of qemu -help. */ - char *qemu_devices; /* Output of qemu -device ? */ - char *qemu_datadirs; /* Output of qemu -L ? (NULL if not supported) */ - - int virtio_scsi; /* See function - guestfs_int_qemu_supports_virtio_scsi */ + sqlite3 *db; /* Database storing qemu detection data. */ + int64_t qemuid; /* qemu_binary.id */ + int virtio_scsi; /* See function + guestfs_int_qemu_supports_virtio_scsi */ }; -static int test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version); +static int db_create_tables (guestfs_h *g, sqlite3 *db); +static int64_t db_check_if_cached (guestfs_h *g, sqlite3 *db, const struct stat *statbuf); +static int64_t db_insert_qemu_binary (guestfs_h *g, sqlite3 *db, const struct stat *statbuf, const char *qemu_path, const char *qemu_help, const struct version *qemu_version); +static int db_select_qemu_version (guestfs_h *g, sqlite3 *db, int64_t qemuid, struct version *qemu_version); +static void db_insert_devices_row (guestfs_h *g, void *vp, const char *buf, size_t len); +static void db_insert_datadirs_row (guestfs_h *g, void *vp, const char *buf, size_t len); +static int64_t probe_qemu_binary (guestfs_h *g, sqlite3 *db, const struct stat *statbuf); static void parse_qemu_version (guestfs_h *g, const char *, struct version *qemu_version); static void read_all (guestfs_h *g, void *retv, const char *buf, size_t len); -/* This is saved in the qemu.stat file, so if we decide to change the - * test_qemu memoization format/data in future, we should increment - * this to discard any memoized data cached by previous versions of - * libguestfs. +/* This is saved in the database filename, so if we decide to change + * the schema in future, we should increment this to discard any + * memoized data cached by previous versions of libguestfs. + * + * NB: If the qemu-schema.sql changes you MUST increment + * SCHEMA_GENERATION. That's so we don't have to deal with updating + * an existing database to the new schema. */ -#define MEMO_GENERATION 1 +#define SCHEMA_GENERATION 2 +extern const char *guestfs_int_qemu_schema; /** - * Test qemu binary (or wrapper) runs, and do C<qemu -help> so we know - * the version of qemu what options this qemu supports, - * C<qemu -device ?> so we know what devices are available, - * and C<qemu -L ?> to list data directories. + * Test qemu binary (or wrapper). * * The version number of qemu (from the C<-help> output) is saved in * C<&qemu_version>. @@ -83,175 +113,354 @@ struct qemu_data * guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version) { struct qemu_data *data; + CLEANUP_FREE char *cachedir = NULL, *qemu_db_filename = NULL; struct stat statbuf; - CLEANUP_FREE char *cachedir = NULL, *qemu_stat_filename = NULL, - *qemu_help_filename = NULL, *qemu_devices_filename = NULL, - *qemu_datadirs_filename = NULL; - FILE *fp; - int generation; - uint64_t prev_size, prev_mtime; - - if (stat (g->hv, &statbuf) == -1) { - perrorf (g, "stat: %s", g->hv); - return NULL; - } + sqlite3 *db; + int r; + int64_t qemuid; + CLEANUP_SQLITE3_FREE char *errmsg = NULL; cachedir = guestfs_int_lazy_make_supermin_appliance_dir (g); if (cachedir == NULL) return NULL; - qemu_stat_filename = safe_asprintf (g, "%s/qemu.stat", cachedir); - qemu_help_filename = safe_asprintf (g, "%s/qemu.help", cachedir); - qemu_devices_filename = safe_asprintf (g, "%s/qemu.devices", cachedir); - qemu_datadirs_filename = safe_asprintf (g, "%s/qemu.datadirs", cachedir); + qemu_db_filename = safe_asprintf (g, "%s/qemu.%d.db", + cachedir, SCHEMA_GENERATION); + + /* Warn if sqlite wasn't compiled to be thread-safe. */ + if (sqlite3_threadsafe () == 0) + warning (g, + _("sqlite3 is not thread safe, use of libguestfs" + " from multiple threads may cause data corruption.")); + + /* Open the database, or create it. */ + r = sqlite3_open (qemu_db_filename, &db); + if (r != SQLITE_OK) { + error (g, "%s: %s", qemu_db_filename, sqlite3_errmsg (db)); + return NULL; + } - /* Did we previously test the same version of qemu? */ - debug (g, "checking for previously cached test results of %s, in %s", - g->hv, cachedir); + sqlite3_busy_timeout (db, 10000 /* ms */); - fp = fopen (qemu_stat_filename, "r"); - if (fp == NULL) - goto do_test; - if (fscanf (fp, "%d %" SCNu64 " %" SCNu64, - &generation, &prev_size, &prev_mtime) != 3) { - fclose (fp); - goto do_test; + /* The create table statements from qemu-schema.sql are designed to + * be safe to run in parallel or to be run if the database has + * already been created. So do those outside the transaction. + */ + if (db_create_tables (g, db) == -1) { + sqlite3_close (db); + return NULL; } - fclose (fp); - if (generation == MEMO_GENERATION && - (uint64_t) statbuf.st_size == prev_size && - (uint64_t) statbuf.st_mtime == prev_mtime) { - /* Same binary as before, so read the previously cached qemu -help - * and qemu -devices ? output. - */ - if (access (qemu_help_filename, R_OK) == -1 || - access (qemu_devices_filename, R_OK) == -1 || - access (qemu_datadirs_filename, R_OK) == -1) - goto do_test; + /* Find out if we've probed this version of qemu before. Do this + * outside the transaction since it only involves a select stmt. + */ + debug (g, "checking for previously cached test results of %s", g->hv); - debug (g, "loading previously cached test results"); + if (stat (g->hv, &statbuf) == -1) { + perrorf (g, "stat: %s", g->hv); + sqlite3_close (db); + return NULL; + } - data = safe_calloc (g, 1, sizeof *data); + qemuid = db_check_if_cached (g, db, &statbuf); + if (qemuid == -1) { + sqlite3_close (db); + return NULL; + } - if (guestfs_int_read_whole_file (g, qemu_help_filename, - &data->qemu_help, NULL) == -1) { - guestfs_int_free_qemu_data (data); + if (qemuid > 0) + /* Previously probed this qemu. This is the fast path. */ + debug (g, "using previously cached test results"); + else { + debug (g, "no previously cached results found, testing qemu binary"); + + /* A new version of qemu. This is the slow path. Begin an + * exclusive transaction while we probe qemu. This will serialize + * all qemu probes, but not the later select statements where we + * retrieve the probed data. + */ + r = sqlite3_exec (db, "begin exclusive transaction", NULL, 0, &errmsg); + if (r != SQLITE_OK) { + error (g, "sqlite3: begin transaction: %s", errmsg); + sqlite3_close (db); return NULL; } - parse_qemu_version (g, data->qemu_help, qemu_version); - - if (guestfs_int_read_whole_file (g, qemu_devices_filename, - &data->qemu_devices, NULL) == -1) { - guestfs_int_free_qemu_data (data); + /* After acquiring the exclusive lock, it may be that another + * thread has probed this qemu binary and inserted the data. If + * we go ahead and insert the same data then we'll get a unique + * constraint violation. Check if that is the case, and skip + * probing if so. + */ + qemuid = db_check_if_cached (g, db, &statbuf); + if (qemuid == -1) + goto rollback; + if (qemuid > 0) + goto skip_probing; + + qemuid = probe_qemu_binary (g, db, &statbuf); + if (qemuid == -1) { + rollback: + /* This will rollback the transaction and release all locks. */ + sqlite3_close (db); return NULL; } - if (guestfs_int_read_whole_file (g, qemu_datadirs_filename, - &data->qemu_datadirs, NULL) == -1) { - guestfs_int_free_qemu_data (data); + skip_probing: + /* Commit the probed data. */ + r = sqlite3_exec (db, "commit transaction", NULL, 0, &errmsg); + if (r != SQLITE_OK) { + error (g, "sqlite3: commit transaction: %s", errmsg); + sqlite3_close (db); return NULL; } - return data; - } - - do_test: - data = safe_calloc (g, 1, sizeof *data); - - if (test_qemu (g, data, qemu_version) == -1) { - guestfs_int_free_qemu_data (data); - return NULL; - } - - /* Now memoize the qemu output in the cache directory. */ - debug (g, "saving test results"); - - fp = fopen (qemu_help_filename, "w"); - if (fp == NULL) { - help_error: - perrorf (g, "%s", qemu_help_filename); - if (fp != NULL) fclose (fp); - guestfs_int_free_qemu_data (data); - return NULL; - } - if (fprintf (fp, "%s", data->qemu_help) == -1) - goto help_error; - if (fclose (fp) == -1) - goto help_error; - - fp = fopen (qemu_devices_filename, "w"); - if (fp == NULL) { - devices_error: - perrorf (g, "%s", qemu_devices_filename); - if (fp != NULL) fclose (fp); - guestfs_int_free_qemu_data (data); - return NULL; - } - if (fprintf (fp, "%s", data->qemu_devices) == -1) - goto devices_error; - if (fclose (fp) == -1) - goto devices_error; - - fp = fopen (qemu_datadirs_filename, "w"); - if (fp == NULL) { - datadirs_error: - perrorf (g, "%s", qemu_datadirs_filename); - if (fp != NULL) fclose (fp); - guestfs_int_free_qemu_data (data); - return NULL; + /* After this point, any select statements to retrieve the data will + * use autocommit. + */ } - if (fprintf (fp, "%s", data->qemu_datadirs) == -1) - goto datadirs_error; - if (fclose (fp) == -1) - goto datadirs_error; - /* Write the qemu.stat file last so that its presence indicates that - * the qemu.help and qemu.devices files ought to exist. + /* Get the version of qemu from the database and return it to the + * caller via the qemu_version struct. */ - fp = fopen (qemu_stat_filename, "w"); - if (fp == NULL) { - stat_error: - perrorf (g, "%s", qemu_stat_filename); - if (fp != NULL) fclose (fp); - guestfs_int_free_qemu_data (data); + if (db_select_qemu_version (g, db, qemuid, qemu_version) == -1) { + sqlite3_close (db); return NULL; } - /* The path to qemu is stored for information only, it is not - * used when we parse the file. - */ - if (fprintf (fp, "%d %" PRIu64 " %" PRIu64 " %s\n", - MEMO_GENERATION, - (uint64_t) statbuf.st_size, - (uint64_t) statbuf.st_mtime, - g->hv) == -1) - goto stat_error; - if (fclose (fp) == -1) - goto stat_error; + data = safe_malloc (g, sizeof *data); + data->qemuid = qemuid; + data->db = db; + data->virtio_scsi = 0; return data; } +/** + * Create the database schema. We use "create table if not exists" so + * that these statements have no effect if we're reopening an existing + * database file. + */ static int -test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version) +db_create_tables (guestfs_h *g, sqlite3 *db) +{ + int r; + CLEANUP_SQLITE3_FREE char *errmsg = NULL; + + r = sqlite3_exec (db, guestfs_int_qemu_schema, NULL, 0, &errmsg); + if (r != SQLITE_OK) { + error (g, "sqlite3: error creating database schema: %s", errmsg); + return -1; + } + + return 0; +} + +/** + * Check for a previously cached qemu result in the database. + * + * Returns qemu ID E<ge> 1. Returns C<0> if not found. Returns + * C<-1> on error. + */ +static int64_t +db_check_if_cached (guestfs_h *g, sqlite3 *db, const struct stat *statbuf) +{ + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; + int64_t qemuid; + + r = sqlite3_prepare_v2 + (db, + "select id from qemu_binary where size = ? and mtime = ?", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + error (g, "sqlite3: prepare: %s", sqlite3_errmsg (db)); + return -1; + } + sqlite3_bind_int64 (stmt, 1, (sqlite3_int64) statbuf->st_size); + sqlite3_bind_int64 (stmt, 2, (sqlite3_int64) statbuf->st_mtime); + + r = sqlite3_step (stmt); + if (r == SQLITE_DONE) /* not found */ + return 0; + + if (r != SQLITE_ROW) { + error (g, "sqlite3: step: %s", sqlite3_errmsg (db)); + return -1; + } + + /* found 1 row */ + qemuid = sqlite3_column_int64 (stmt, 0); + assert (qemuid > 0); + return qemuid; +} + +/** + * Insert a row in the qemu_binary table recording that we have + * cached data for this binary. Returns the qemuid E<ge> 1, or + * C<-1> on error. + */ +static int64_t +db_insert_qemu_binary (guestfs_h *g, sqlite3 *db, + const struct stat *statbuf, + const char *qemu_path, + const char *qemu_help, + const struct version *qemu_version) +{ + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; + int64_t qemuid; + + r = sqlite3_prepare_v2 + (db, + "insert into qemu_binary (id, size, mtime, path, help," + " major, minor, release)" + " values (NULL, ?, ?, ?, ?, ?, ?, ?)", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + error (g, "sqlite3: prepare: %s", sqlite3_errmsg (db)); + return -1; + } + sqlite3_bind_int64 (stmt, 1, (sqlite3_int64) statbuf->st_size); + sqlite3_bind_int64 (stmt, 2, (sqlite3_int64) statbuf->st_mtime); + sqlite3_bind_text (stmt, 3, qemu_path, -1, SQLITE_TRANSIENT); + sqlite3_bind_text (stmt, 4, qemu_help, -1, SQLITE_TRANSIENT); + sqlite3_bind_int (stmt, 5, qemu_version->v_major); + sqlite3_bind_int (stmt, 6, qemu_version->v_minor); + sqlite3_bind_int (stmt, 7, qemu_version->v_micro); + + r = sqlite3_step (stmt); + if (r != SQLITE_DONE) { + error (g, "sqlite3: insert: %s", sqlite3_errmsg (db)); + return -1; + } + + qemuid = sqlite3_last_insert_rowid (db); + assert (qemuid > 0); + return qemuid; +} + +/** + * Read the qemu version from the database. + */ +static int +db_select_qemu_version (guestfs_h *g, sqlite3 *db, + int64_t qemuid, struct version *qemu_version) +{ + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; + + r = sqlite3_prepare_v2 + (db, + "select major, minor, release from qemu_binary where id = ?", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + error (g, "sqlite3: prepare: %s", sqlite3_errmsg (db)); + return -1; + } + sqlite3_bind_int64 (stmt, 1, qemuid); + + r = sqlite3_step (stmt); + if (r != SQLITE_ROW) { + error (g, "sqlite3: step: %s", sqlite3_errmsg (db)); + return -1; + } + + guestfs_int_version_from_values (qemu_version, + sqlite3_column_int (stmt, 0), + sqlite3_column_int (stmt, 1), + sqlite3_column_int (stmt, 2)); + return 0; +} + +struct db_qemuid { + sqlite3 *db; + unsigned db_errors; + int64_t qemuid; +}; + +static void +db_insert_devices_row (guestfs_h *g, void *vp, const char *buf, size_t len) +{ + struct db_qemuid *db_qemuid = vp; + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; + + r = sqlite3_prepare_v2 + (db_qemuid->db, + "insert into qemu_devices (qemuid, device) values (?, ?)", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + db_qemuid->db_errors++; + return; + } + sqlite3_bind_int64 (stmt, 1, db_qemuid->qemuid); + sqlite3_bind_text (stmt, 2, buf, len, SQLITE_TRANSIENT); + + r = sqlite3_step (stmt); + if (r != SQLITE_DONE) + db_qemuid->db_errors++; +} + +static void +db_insert_datadirs_row (guestfs_h *g, void *vp, const char *buf, size_t len) +{ + struct db_qemuid *db_qemuid = vp; + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; + + r = sqlite3_prepare_v2 + (db_qemuid->db, + "insert into qemu_datadirs (id, qemuid, datadir) values (NULL, ?, ?)", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + db_qemuid->db_errors++; + return; + } + sqlite3_bind_int64 (stmt, 1, db_qemuid->qemuid); + sqlite3_bind_text (stmt, 2, buf, len, SQLITE_TRANSIENT); + + r = sqlite3_step (stmt); + if (r != SQLITE_DONE) + db_qemuid->db_errors++; +} + +static int64_t +probe_qemu_binary (guestfs_h *g, sqlite3 *db, const struct stat *statbuf) { CLEANUP_CMD_CLOSE struct command *cmd1 = guestfs_int_new_command (g); CLEANUP_CMD_CLOSE struct command *cmd2 = guestfs_int_new_command (g); CLEANUP_CMD_CLOSE struct command *cmd3 = guestfs_int_new_command (g); + CLEANUP_FREE char *qemu_help = NULL; int r; + int64_t qemuid; + struct db_qemuid db_qemuid; + struct version qemu_version; guestfs_int_cmd_add_arg (cmd1, g->hv); guestfs_int_cmd_add_arg (cmd1, "-display"); guestfs_int_cmd_add_arg (cmd1, "none"); guestfs_int_cmd_add_arg (cmd1, "-help"); - guestfs_int_cmd_set_stdout_callback (cmd1, read_all, &data->qemu_help, + guestfs_int_cmd_set_stdout_callback (cmd1, read_all, &qemu_help, CMD_STDOUT_FLAG_WHOLE_BUFFER); r = guestfs_int_cmd_run (cmd1); - if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) - goto error; + if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) { + cmd_error: + if (r == -1) + return -1; + guestfs_int_external_command_failed (g, r, g->hv, NULL); + return -1; + } - parse_qemu_version (g, data->qemu_help, qemu_version); + parse_qemu_version (g, qemu_help, &qemu_version); + + qemuid = db_insert_qemu_binary (g, db, statbuf, g->hv, + qemu_help, &qemu_version); + if (qemuid == -1) + return -1; + + db_qemuid.db = db; + db_qemuid.db_errors = 0; + db_qemuid.qemuid = qemuid; guestfs_int_cmd_add_arg (cmd2, g->hv); guestfs_int_cmd_add_arg (cmd2, "-display"); @@ -266,14 +475,18 @@ test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version) guestfs_int_cmd_add_arg (cmd2, "?"); guestfs_int_cmd_clear_capture_errors (cmd2); guestfs_int_cmd_set_stderr_to_stdout (cmd2); - guestfs_int_cmd_set_stdout_callback (cmd2, read_all, &data->qemu_devices, - CMD_STDOUT_FLAG_WHOLE_BUFFER); + guestfs_int_cmd_set_stdout_callback (cmd2, db_insert_devices_row, + &db_qemuid, 0); r = guestfs_int_cmd_run (cmd2); if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) - goto error; + goto cmd_error; + if (db_qemuid.db_errors != 0) { + error (g, "sqlite3: insert: %s", sqlite3_errmsg (db)); + return -1; + } /* qemu -L ? only supported in qemu >= 2.7 */ - if (guestfs_int_version_ge (qemu_version, 2, 6 /* XXX 7 */, 0)) { + if (guestfs_int_version_ge (&qemu_version, 2, 6 /* XXX 7 */, 0)) { guestfs_int_cmd_add_arg (cmd3, g->hv); guestfs_int_cmd_add_arg (cmd3, "-display"); guestfs_int_cmd_add_arg (cmd3, "none"); @@ -285,23 +498,18 @@ test_qemu (guestfs_h *g, struct qemu_data *data, struct version *qemu_version) "accel=kvm:tcg"); guestfs_int_cmd_add_arg (cmd3, "-L"); guestfs_int_cmd_add_arg (cmd3, "?"); - guestfs_int_cmd_set_stdout_callback (cmd3, read_all, &data->qemu_datadirs, - CMD_STDOUT_FLAG_WHOLE_BUFFER); + guestfs_int_cmd_set_stdout_callback (cmd3, db_insert_datadirs_row, + &db_qemuid, 0); r = guestfs_int_cmd_run (cmd3); if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) - goto error; + goto cmd_error; + if (db_qemuid.db_errors != 0) { + error (g, "sqlite3: insert: %s", sqlite3_errmsg (db)); + return -1; + } } - else - data->qemu_datadirs = safe_strdup (g, ""); - return 0; - - error: - if (r == -1) - return -1; - - guestfs_int_external_command_failed (g, r, g->hv, NULL); - return -1; + return qemuid; } /** @@ -353,19 +561,77 @@ int guestfs_int_qemu_supports (guestfs_h *g, const struct qemu_data *data, const char *option) { - return strstr (data->qemu_help, option) != NULL; + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; + + assert (data->db != NULL); + assert (data->qemuid > 0); + assert (strchr (option, '%') == NULL); /* SQL '%' is wildcard */ + + r = sqlite3_prepare_v2 + (data->db, + "select 1 from qemu_binary where id = ? and help like '%' || ? || '%'", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + error (g, "sqlite3: prepare: %s", sqlite3_errmsg (data->db)); + return -1; + } + sqlite3_bind_int64 (stmt, 1, data->qemuid); + sqlite3_bind_text (stmt, 2, option, -1, SQLITE_TRANSIENT); + + r = sqlite3_step (stmt); + if (r == SQLITE_DONE) /* not found */ + return 0; + + if (r != SQLITE_ROW) { + error (g, "sqlite3: step: %s", sqlite3_errmsg (data->db)); + return -1; + } + + return 1; } /** - * Test if device is supported by qemu (currently just greps the - * C<qemu -device ?> output). + * Test if device is supported by qemu. */ int guestfs_int_qemu_supports_device (guestfs_h *g, const struct qemu_data *data, const char *device_name) { - return strstr (data->qemu_devices, device_name) != NULL; + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; + + assert (data->db != NULL); + assert (data->qemuid > 0); + assert (strchr (device_name, '%') == NULL); /* SQL '%' is wildcard */ + + /* Each line from qemu -device ? looks like: + * name "i6300esb", bus PCI + */ + r = sqlite3_prepare_v2 + (data->db, + "select 1 from qemu_devices" + " where qemuid = ?" + " and device like 'name \"' || ? || '\", %'", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + error (g, "sqlite3: prepare: %s", sqlite3_errmsg (data->db)); + return -1; + } + sqlite3_bind_int64 (stmt, 1, data->qemuid); + sqlite3_bind_text (stmt, 2, device_name, -1, SQLITE_TRANSIENT); + + r = sqlite3_step (stmt); + if (r == SQLITE_DONE) /* not found */ + return 0; + + if (r != SQLITE_ROW) { + error (g, "sqlite3: step: %s", sqlite3_errmsg (data->db)); + return -1; + } + + return 1; } /** @@ -376,22 +642,40 @@ guestfs_int_qemu_supports_bios (guestfs_h *g, const struct qemu_data *data, const char *bios_name) { - CLEANUP_FREE_STRING_LIST char **datadirs; - size_t i; + CLEANUP_SQLITE3_FINALIZE sqlite3_stmt *stmt = NULL; + int r; - datadirs = guestfs_int_split_string ('\n', data->qemu_datadirs); - if (datadirs == NULL) - return 0; /* ignore errors, return false which is safe */ + assert (data->db != NULL); + assert (data->qemuid > 0); + assert (strchr (bios_name, '%') == NULL); /* SQL '%' is wildcard */ - for (i = 0; datadirs[i] != NULL; ++i) { + r = sqlite3_prepare_v2 + (data->db, + "select datadir from qemu_datadirs" + " where qemuid = ?" + " order by id", + -1, &stmt, NULL); + if (r != SQLITE_OK) { + error (g, "sqlite3: prepare: %s", sqlite3_errmsg (data->db)); + return -1; + } + sqlite3_bind_int64 (stmt, 1, data->qemuid); + + while ((r = sqlite3_step (stmt)) == SQLITE_ROW) { + const char *datadir = (const char *) sqlite3_column_text (stmt, 0); CLEANUP_FREE char *path; - path = safe_asprintf (g, "%s/%s", datadirs[i], bios_name); + path = safe_asprintf (g, "%s/%s", datadir, bios_name); if (access (path, R_OK) == 0) return 1; } - return 0; + if (r != SQLITE_DONE) { + error (g, "sqlite3: step: %s", sqlite3_errmsg (data->db)); + return -1; + } + + return 0; /* BIOS not found. */ } static int @@ -777,9 +1061,7 @@ void guestfs_int_free_qemu_data (struct qemu_data *data) { if (data) { - free (data->qemu_help); - free (data->qemu_devices); - free (data->qemu_datadirs); + sqlite3_close (data->db); free (data); } } -- 2.7.4
Reasonably Related Threads
- [PATCH v2 0/2] Use -bios bios-fast.bin where supported.
- [PATCH 0/2] Use -bios bios-fast.bin where supported.
- [PATCH 0/4] lib: qemu: Add test for mandatory locking.
- [PATCH v3 0/6] launch: direct: Disable qemu locking when opening drives readonly.
- [PATCH v2 0/5] launch: direct: Disable qemu locking when opening drives readonly (RHBZ#1417306)