*~
+*.o
+.deps
Makefile
Makefile.in
aclocal.m4
autom4te.cache
compile
+config.h
+config.h.in
config.log
config.status
configure
+depcomp
install-sh
missing
src/Makefile
-src/Makefile.in
\ No newline at end of file
+src/Makefile.in
+stamp-h1
\ No newline at end of file
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ACLOCAL_AMFLAGS = -I m4
+
SUBDIRS = src
dnl Check support for 64 bit file offsets.
AC_SYS_LARGEFILE
+AC_GNU_SOURCE
+
# Generate output files.
+AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+bin_PROGRAMS = virt-kernel-info
+virt_kernel_info_SOURCES = \
+ internal.h \
+ main.c \
+ output.c
+
+# Plugins:
+virt_kernel_info_SOURCES += \
+ dmesg.c
+
+virt_kernel_info_CFLAGS = -DDATADIR=\"$(datadir)\"
--- /dev/null
+/* Kernel info for virtual domains.
+ * (C) Copyright 2008-2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "internal.h"
+
+static void init (void) ATTRIBUTE_CONSTRUCTOR;
+static void run (/*struct kimage * */ void);
+
+static struct tool me = {
+ .name = "dmesg",
+ .external_cmd = 1,
+ .run_fn = run,
+};
+
+static void
+init (void)
+{
+ me.summary = _("show kernel messages from a Linux virtual machine");
+ me.description =
+ _("\
+This tool prints the kernel messages ('dmesg') from a running
+Linux virtual machine.\n\
+");
+ register_tool (&me);
+}
+
+static void
+run (/*struct kimage *kimage*/)
+{
+ NOT_IMPL;
+}
--- /dev/null
+/* Kernel info for virtual domains.
+ * (C) Copyright 2008-2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef INTERNAL_H
+#define INTERNAL_H
+
+#include <libintl.h>
+
+#ifdef __GNUC__
+
+#ifndef __GNUC_PREREQ
+#define __GNUC_PREREQ(maj,min) 0
+#endif
+
+#define ATTRIBUTE_UNUSED __attribute__((__unused__))
+#define ATTRIBUTE_FORMAT(args...) __attribute__((__format__ (args)))
+#define ATTRIBUTE_NORETURN __attribute__((__noreturn__))
+#define ATTRIBUTE_CONSTRUCTOR __attribute__((__constructor__))
+
+#if __GNUC_PREREQ (3, 4)
+#define ATTRIBUTE_RETURN_CHECK __attribute__((__warn_unused_result__))
+#else
+#define ATTRIBUTE_RETURN_CHECK
+#endif
+
+#else /* !__GNUC__ */
+#define ATTRIBUTE_UNUSED
+#define ATTRIBUTE_FORMAT(...)
+#define ATTRIBUTE_NORETURN
+#error "we must be able to define attribute((constructor)) for this compiler"
+#define ATTRIBUTE_RETURN_CHECK
+#endif /* __GNUC__ */
+
+/* Gettext functions.
+ *
+ * PACKAGE (usually the string "virt-kernel-info") should be the same as the
+ * $(DOMAIN) variable declared in po/Makevars.
+ */
+#define _(str) dgettext(PACKAGE, (str))
+#define N_(str) dgettext(PACKAGE, (str))
+
+/* String equality tests, suggested by Jim Meyering. */
+#define STREQ(a,b) (strcmp((a),(b)) == 0)
+#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
+#define STRNEQ(a,b) (strcmp((a),(b)) != 0)
+#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
+#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
+#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
+#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
+#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
+#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
+
+/* Each tool describes itself to the main program by
+ * defining and registering this structure.
+ */
+typedef void (*tool_run_fn) (void); /* XXX */
+
+struct tool {
+ const char *name; /* Tool name, eg. "ps" */
+
+ int external_cmd; /* Is the tool an external command, eg. "virt-ps" */
+
+ const char *summary; /* Tool-specific one line summary. */
+ const char *description; /* Long description. */
+
+ tool_run_fn run_fn; /* Tool entry point. */
+
+ /* The main program links tools after they are registered
+ * through this field.
+ */
+ struct tool *next;
+};
+
+/* Register a tool with the main program. */
+extern void register_tool (struct tool *me);
+
+/* Error printing functions:
+ *
+ * error() will print an error and exit. Only use this if the error
+ * really is fatal. NOT usually used inside tools, because they can
+ * be called repeatedly for different domains.
+ *
+ * warning() will print a warning message and continue. Use this
+ * inside tools (and return), instead of exiting, because the tool
+ * can be called repeatedly for different domains.
+ *
+ * internal_error() is like error() but used for errors that shouldn't
+ * happen.
+ *
+ * INTERNAL_ERROR is also used for errors which shouldn't happen, but
+ * prints only file and line number.
+ *
+ * NOT_IMPL is used to mark functions and code sections which are not
+ * implemented.
+ *
+ * All of these functions append \n after the message.
+ */
+extern void error (const char *fs, ...)
+ ATTRIBUTE_FORMAT(printf, 1, 2) ATTRIBUTE_NORETURN;
+extern void warning (const char *fs, ...)
+ ATTRIBUTE_FORMAT(printf, 1, 2);
+extern void internal_error (const char *fs, ...)
+ ATTRIBUTE_FORMAT(printf, 1, 2) ATTRIBUTE_NORETURN;
+
+#define INTERNAL_ERROR \
+ internal_error (_("%s:%d: internal error"), \
+ __FILE__, __LINE__)
+
+#define NOT_IMPL \
+ internal_error (_("%s:%d: this feature is not implemented"), \
+ __FILE__, __LINE__)
+
+/* Output functions. These handle CSV output automatically, but
+ * must be used with care.
+ *
+ * output_heading() is used to write the heading (title) row.
+ * The format string is converted such that any %-sequence which
+ * _isn't_ %s is turned into %s. (So only string fields are
+ * expected in headings).
+ *
+ * output_row() is used to write each row of data. The format
+ * string is used directly for non-CSV output, so this is mostly
+ * equivalent to printf(). However for CSV output, the format
+ * string is used to tell the types of each field, other characters
+ * being ignored.
+ *
+ * Only fairly simple format strings are understood.
+ *
+ * Usual usage is:
+ *
+ * const char *fs = "%-9d %s";
+ *
+ * output_heading (fs, _("PID"), _("NAME"));
+ * for (i = 0; i < nr_procs; ++i) {
+ * output_row (fs, proc[i].pid, proc[i].name);
+ * }
+ *
+ * A \n is added automatically after each heading/row.
+ */
+extern void output_heading (const char *fs, ...)
+ /* not ATTRIBUTE_FORMAT */;
+extern void output_row (const char *fs, ...)
+ ATTRIBUTE_FORMAT(printf, 1, 2);
+
+/* Global flags. */
+extern int csv;
+extern int debug;
+
+#endif /* INTERNAL_H */
--- /dev/null
+/* Kernel info for virtual domains.
+ * (C) Copyright 2008-2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <getopt.h>
+
+#include <libvirt/libvirt.h>
+#include <libvirt/virterror.h>
+
+#include "internal.h"
+
+/* Global flags. */
+int csv = 0;
+int debug = 0;
+
+/* Linked list of registered tools. */
+static struct tool *tools = NULL;
+
+/* Currently selected tool (may be NULL). */
+struct tool *tool = NULL;
+
+/* External DB location. */
+static const char *externaldb = DATADIR "/" PACKAGE_NAME "/kerneldb";
+static int fail_if_no_externaldb = 0;
+
+/* URI and libvirt connection. */
+static const char *uri = NULL;
+static virConnectPtr conn = NULL;
+
+/* If -t option was passed, this is the filename of the test image. */
+static const char *test_image = NULL;
+
+/* If --list-kernels was passed. */
+static int list_kernels = 0;
+
+/* Local functions. */
+static void load_externaldb (const char *, int);
+static void do_list_kernels (void) ATTRIBUTE_NORETURN;
+static void usage (void) ATTRIBUTE_NORETURN;
+
+static virDomainPtr *get_named_domains (char * const*, int);
+static virDomainPtr *get_all_domains (void);
+
+int
+main (int argc, char *argv[])
+{
+ int c;
+
+ /* argv[0] can be the name of the tool, or if not recognized then
+ * the name of the tool must be the first anonymous argument.
+ */
+ if (argv[0]) {
+ const char *prog = strrchr (argv[0], '/');
+
+ if (!prog)
+ prog = argv[0];
+
+ if (STRCASEEQLEN (prog+1, "virt-", 5)) {
+ struct tool *t;
+
+ prog += 6;
+
+ for (t = tools; t != NULL; t = t->next) {
+ if (STRCASEEQ (prog, t->name)) {
+ tool = t;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Parse command line parameters. */
+ while (1) {
+ static const char *shortopts = "A:E:T:W:c:dt:x:?";
+ static struct option longopts[] = {
+ { "arch", required_argument, 0, 'A' },
+ { "endian", required_argument, 0, 'E' },
+ { "kernel-min", required_argument, 0, 0 },
+ { "kernel-max", required_argument, 0, 0 },
+ { "text", required_argument, 0, 'T' },
+ { "wordsize", required_argument, 0, 'W' },
+ { "connect", required_argument, 0, 'c' },
+ { "csv", no_argument, 0, 0 },
+ { "debug", no_argument, 0, 'd' },
+ { "help", no_argument, 0, '?' },
+ { "list-kernels", no_argument, 0, 0 },
+ { "image", required_argument, 0, 't' },
+ { "externaldb", required_argument, 0, 'x' },
+ { "version", no_argument, 0, 0 }
+ };
+ int option_index = 0;
+
+ c = getopt_long (argc, argv, shortopts, longopts, &option_index);
+ if (c == -1) break;
+
+ switch (c) {
+ case 0: { /* longopt without short equivalent */
+ const char *longopt_name = longopts[option_index].name;
+
+ if (STRCASEEQ (longopt_name, "csv")) /* csv output */
+ csv = 1;
+ /* list kernels and exit */
+ else if (STRCASEEQ (longopt_name, "list-kernels"))
+ list_kernels = 1;
+ /* set kernel-min */
+ else if (STRCASEEQ (longopt_name, "kernel-min"))
+ NOT_IMPL;
+ /* set kernel-max */
+ else if (STRCASEEQ (longopt_name, "kernel-max"))
+ NOT_IMPL;
+ /* display version and exit */
+ else if (STRCASEEQ (longopt_name, "version")) {
+ printf ("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
+ exit (0);
+ }
+ else
+ INTERNAL_ERROR; /* this shouldn't happen */
+
+ break;
+ }
+
+ case 'A': /* set architecture */
+ NOT_IMPL;
+ case 'E': /* set endianness */
+ NOT_IMPL;
+ case 'T': /* set text address */
+ NOT_IMPL;
+ case 'W': /* set wordsize */
+ NOT_IMPL;
+
+ case 'c': /* connect to URI */
+ uri = optarg;
+ break;
+
+ case 'd': /* enable debugging */
+ debug = 1;
+ break;
+
+ case 't': /* load kernel image */
+ test_image = optarg;
+ break;
+
+ case 'x': /* location of external database */
+ externaldb = optarg;
+ fail_if_no_externaldb = 1;
+ break;
+
+ case '?': /* print help */
+ usage ();
+
+ default:
+ INTERNAL_ERROR; /* this shouldn't happen */
+ }
+ } /* while */
+
+ /* Load the external database / kernel updates, if necessary. */
+ load_externaldb (externaldb, fail_if_no_externaldb);
+
+ /* If asked, list kernels and exit. */
+ if (list_kernels)
+ do_list_kernels ();
+
+ /* If we haven't got a tool name yet, then the first anon parameter
+ * must be a tool name.
+ */
+ if (!tool) {
+ if (optind < argc) {
+ const char *p;
+ struct tool *t;
+
+ p = argv[optind++];
+
+ for (t = tools; t != NULL; t = t->next) {
+ if (STRCASEEQ (p, t->name)) {
+ tool = t;
+ goto found_tool;
+ }
+ }
+
+ error (_("'%s' is not a recognized virt-kernel-info tool.\n\nMake sure you specify the virt-kernel-info tool as the first argument on the command line. To get a list of recognized tools, use 'virt-kernel-info --help'."), p);
+ } else
+ error (_("Could not work out which virt-kernel-info tool you are trying to use.\n\nMake sure you specify the virt-kernel-info tool as the first argument on the command line. To get a list of recognized tools, use 'virt-kernel-info --help'."));
+
+ found_tool: ;
+ }
+
+ /* We should have worked out which tool it is by now. */
+ assert (tool != NULL);
+
+ /* If -t was passed, then we load that forensic image, else we have
+ * to connect to libvirt.
+ */
+ if (test_image) {
+ if (optind < argc)
+ error (_("If '-t' is passed then there shouldn't be any additional command line arguments."));
+
+ NOT_IMPL;
+ }
+ else {
+ virDomainPtr *doms;
+
+ /* Connect to libvirt. */
+ conn = virConnectOpenReadOnly (uri);
+ if (!conn)
+ error (_("failed to connect to hypervisor '%s'.\nWhen connecting to some hypervisors you may need to be running as root.\nThere may be a more detailed error message above this, but if not then we didn't print one because libvirt's virterror mechanism is next to useless."),
+ uri != NULL ? uri : _("default"));
+
+ /* Extra parameters are a list of domain names, IDs or UUIDs. */
+ if (optind < argc)
+ doms = get_named_domains (&argv[optind], argc - optind);
+ else
+ doms = get_all_domains ();
+
+ /* Act on each domain. */
+ NOT_IMPL;
+ }
+
+ exit (0);
+}
+
+/* Usage. */
+static void usage (void)
+{
+ struct tool *t;
+
+ printf (_("\
+virt-kernel-info: Tools for providing information about virtual machines\n\
+\n\
+General usage is:\n\
+ <tool> [-options]\n\
+ <tool> [-options] [domain-name|ID|UUID ...]\n\
+ <tool> [-options] -t <image>\n\
+where <tool> is 'virt-ps', 'virt-dmesg' etc. (full list below) or a\n\
+subtool such as 'virt-kernel-info ps', 'virt-kernel-info capture' etc.\n\
+\n\
+General options:\n\
+ --connect | -c <libvirt-uri> Connect to URI (default: autodetect)\n\
+ --csv Output in CSV format for spreadsheets etc.\n\
+ --debug Print extra debugging information\n\
+ --list-kernels List known kernels, then exit\n\
+ --image | -t <image> Examine saved image (see: virt-kernel-info capture)\n\
+ --externaldb | -x <externaldb> Load/override external kernels database\n\
+ --version Print program name and version, then exit\n\
+\n\
+These options may be used to override automatic detection of guest\n\
+architecture, endianness, kernel location, etc.:\n\
+ -A | --arch auto | <arch> | ...\n\
+ -E | --endian auto | le | be\n\
+ --kernel-min auto | <arch> | ... | 0x<addr>\n\
+ --kernel-max auto | <arch> | ... | 0x<addr>\n\
+ -T | --text auto | <arch> | ... | 0x<addr>\n\
+ -W | --wordsize auto | 32 | 64\n\
+where <arch> is the name of an architecture such as i386, x86-64, etc.\n\
+\n\
+List of tools:\n\
+"));
+
+ for (t = tools; t != NULL; t = t->next) {
+ printf ("\n");
+
+ if (t->external_cmd)
+ printf (" virt-%-12s - %s\n", t->name, t->summary);
+ else
+ printf (" virt-kernel-info %8s - %s\n", t->name, t->summary);
+
+ printf ("%s", t->description);
+ if (t->next) printf ("\n");
+ }
+
+ exit (0);
+}
+
+static void
+load_externaldb (const char *externaldb, int fail_if_no_externaldb)
+{
+
+
+
+
+
+
+
+
+ NOT_IMPL;
+}
+
+static void
+do_list_kernels (void)
+{
+ NOT_IMPL;
+}
+
+static virDomainPtr *
+get_named_domains (char * const *domains, int nr_domains)
+{
+ NOT_IMPL;
+}
+
+static virDomainPtr *
+get_all_domains (void)
+{
+ NOT_IMPL;
+}
+
+/* Tools register themselves by calling this function. Note that the
+ * function is called from constructors. In particular it is called
+ * before main(). Also can be called in unspecified order.
+ */
+void
+register_tool (struct tool *tool)
+{
+ struct tool *t, **p;
+
+ /* Insertion sort. */
+ p = &tools;
+ for (t = tools; t != NULL; t = t->next) {
+ if (strcmp (t->name, tool->name) < 0)
+ goto insert;
+ p = &t->next;
+ }
+ insert:
+ tool->next = *p;
+ *p = tool;
+}
+
+/* Warning, error message functions. See internal.h for usage. */
+static void
+message (const char *pre, const char *fs, va_list args)
+{
+ fprintf (stderr, "%s-%s: %s: ", PACKAGE_NAME, PACKAGE_VERSION, pre);
+ vfprintf (stderr, fs, args);
+ fprintf (stderr, "\n");
+}
+
+void
+error (const char *fs, ...)
+{
+ va_list args;
+ va_start (args, fs);
+ message (_("error"), fs, args);
+ va_end (args);
+ exit (1);
+}
+
+void
+warning (const char *fs, ...)
+{
+ va_list args;
+ va_start (args, fs);
+ message (_("warning"), fs, args);
+ va_end (args);
+}
+
+void
+internal_error (const char *fs, ...)
+{
+ va_list args;
+ va_start (args, fs);
+ message (_("internal error"), fs, args);
+ va_end (args);
+ abort ();
+}
--- /dev/null
+/* Kernel info for virtual domains.
+ * (C) Copyright 2008-2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Generic output routines which can write either plain
+ * text tables or CSV files.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "internal.h"
+
+static char *convert_fs_to_strings (const char *fs);
+static void output_to_csv (int to_strings, const char *fs, va_list args);
+
+void
+output_heading (const char *fs, ...)
+{
+ va_list args;
+
+ va_start (args, fs);
+
+ if (!csv) {
+ /* Convert each %..x in the format string into %..s */
+ char *fs2 = convert_fs_to_strings (fs);
+ vprintf (fs2, args);
+ free (fs2);
+ printf ("\n");
+ } else
+ output_to_csv (1, fs, args);
+
+ va_end (args);
+}
+
+void
+output_row (const char *fs, ...)
+{
+ va_list args;
+
+ va_start (args, fs);
+
+ if (!csv) {
+ vprintf (fs, args);
+ printf ("\n");
+ }
+ else
+ output_to_csv (0, fs, args);
+
+ va_end (args);
+}
+
+/* Return a newly allocated format string with each '%..x' sequence
+ * converted to '%..s'. We only attempt to parse simple format
+ * strings, and we assume the length of the format string won't
+ * change.
+ */
+static const char *end_of_specifier = "diouxXeEfFgGaAcsp%";
+
+static char *
+convert_fs_to_strings (const char *fs)
+{
+ char *fs2;
+ int i, len;
+
+ len = strlen (fs);
+ fs2 = strdup (fs);
+ for (i = 0; i < len; ++i) {
+ if (fs2[i] == '%') {
+ /* Search for the end of the %-sequence. */
+ do { i++; }
+ while (i < len && strchr (end_of_specifier, fs2[i]) == NULL);
+
+ if (i == len)
+ internal_error (_("unrecognized format string in output_heading function"));
+
+ fs2[i] = 's';
+ }
+ }
+
+ return fs2; /* caller frees */
+}
+
+/* Output a row to a CSV file. The format string is just used to
+ * get the correct types for each parameter - any non-%-specifiers
+ * in the format string are ignored. If to_strings is true, then
+ * each parameter is assumed to be a string.
+ *
+ * The CSV output is very conservative, designed to be correct
+ * rather than concise.
+ */
+static void
+output_to_csv (int to_strings, const char *fs, va_list args)
+{
+ int i, j, len, comma = 0;
+ char fs2[16];
+
+ len = strlen (fs);
+
+ for (i = 0; i < len; ++i) {
+ /* Look for the next %-specifier. */
+ if (fs[i] == '%') {
+ j = 0;
+ do { fs2[j++] = fs[i++]; }
+ while (j < sizeof (fs2) - 1 &&
+ i < len &&
+ strchr (end_of_specifier, fs[i]) == NULL);
+
+ if (j == sizeof (fs2) - 1 || i == len)
+ internal_error (_("unrecognized format string in output_* function"));
+
+ fs2[j] = fs[i++];
+ fs2[j+1] = '\0';
+ }
+
+ if (fs2[j] != '%') {
+ char *str;
+ int len2;
+
+ if (to_strings)
+ fs2[j] = 's';
+
+ /* Convert the next parameter into a string. */
+ if (vasprintf (&str, fs2, args) == -1)
+ internal_error (_("unable to convert next argument to string using '%s'"),
+ fs2);
+ len2 = strlen (str);
+
+ /* Output the next parameter as a CSV field. */
+ if (comma)
+ putchar (',');
+ comma = 1;
+
+ putchar ('"');
+ for (j = 0; j < len2; ++j) {
+ switch (str[j]) {
+ case '"':
+ putchar ('"');
+ putchar ('"');
+ break;
+ case '\0':
+ putchar ('"');
+ putchar ('0');
+ break;
+ default:
+ putchar (str[j]);
+ }
+ }
+ putchar ('"');
+
+ free (str);
+ }
+ }
+
+ putchar ('\n');
+}