Richard W.M. Jones
2010-Nov-15 14:20 UTC
[Libguestfs] [PATCH 0/3] Make listing applications into a core API
After these three patches, virt-inspector is just a shell around the core API, left doing command line parsing. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html
Richard W.M. Jones
2010-Nov-15 14:21 UTC
[Libguestfs] [PATCH 1/3] inspect: Centralize all file downloads through a single function.
This patch is a clean-up, but it makes more sense in the light of the subsequent patches. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora -------------- next part -------------->From 698abd298499448c6b12c0fd8ca9cb355ab5a670 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Mon, 15 Nov 2010 12:40:02 +0000 Subject: [PATCH 1/3] inspect: Centralize all file downloads through a single function. --- src/inspect.c | 84 +++++++++++++++++++++++++++++++++++--------------------- 1 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/inspect.c b/src/inspect.c index 962fd00..4533746 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -207,6 +207,7 @@ static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, static char *resolve_fstab_device (guestfs_h *g, const char *spec); static void check_package_format (guestfs_h *g, struct inspect_fs *fs); static void check_package_management (guestfs_h *g, struct inspect_fs *fs); +static int download_to_tmp (guestfs_h *g, const char *filename, char *localtmp, int64_t max_size); static int check_for_filesystem_on (guestfs_h *g, const char *device) @@ -927,12 +928,7 @@ check_windows_arch (guestfs_h *g, struct inspect_fs *fs) static int check_windows_registry (guestfs_h *g, struct inspect_fs *fs) { - TMP_TEMPLATE_ON_STACK (dir); -#define dir_len (strlen (dir)) -#define software_hive_len (dir_len + 16) - char software_hive[software_hive_len]; -#define cmd_len (dir_len + 16) - char cmd[cmd_len]; + TMP_TEMPLATE_ON_STACK (software_local); size_t len = strlen (fs->windows_systemroot) + 64; char software[len]; @@ -950,25 +946,10 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs) hive_h *h = NULL; hive_value_h *values = NULL; - /* Security: Refuse to download registry if it is huge. */ - int64_t size = guestfs_filesize (g, software_path); - if (size == -1 || size > 100000000) { - error (g, _("size of %s unreasonable (%" PRIi64 " bytes)"), - software_path, size); + if (download_to_tmp (g, software_path, software_local, 100000000) == -1) goto out; - } - - if (mkdtemp (dir) == NULL) { - perrorf (g, "mkdtemp"); - goto out; - } - snprintf (software_hive, software_hive_len, "%s/software", dir); - - if (guestfs_download (g, software_path, software_hive) == -1) - goto out; - - h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0); + h = hivex_open (software_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0); if (h == NULL) { perrorf (g, "hivex_open"); goto out; @@ -1045,15 +1026,9 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs) free (values); free (software_path); - /* Free up the temporary directory. Note the directory name cannot - * contain shell meta-characters because of the way it was - * constructed above. - */ - snprintf (cmd, cmd_len, "rm -rf %s", dir); - ignore_value (system (cmd)); -#undef dir_len -#undef software_hive_len -#undef cmd_len + /* Free up the temporary file. */ + unlink (software_local); +#undef software_local_len return ret; } @@ -1449,6 +1424,51 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root) return ret; } +/* Download to a guest file to a local temporary file. Refuse to + * download the guest file if it is larger than max_size. The caller + * is responsible for deleting the temporary file after use. + */ +static int +download_to_tmp (guestfs_h *g, const char *filename, + char *localtmp, int64_t max_size) +{ + int fd; + char buf[32]; + int64_t size; + + size = guestfs_filesize (g, filename); + if (size == -1) + /* guestfs_filesize failed and has already set error in handle */ + return -1; + if (size > max_size) { + error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), + filename, size); + return -1; + } + + fd = mkstemp (localtmp); + if (fd == -1) { + perrorf (g, "mkstemp"); + return -1; + } + + snprintf (buf, sizeof buf, "/dev/fd/%d", fd); + + if (guestfs_download (g, filename, buf) == -1) { + close (fd); + unlink (localtmp); + return -1; + } + + if (close (fd) == -1) { + perrorf (g, "close: %s", localtmp); + unlink (localtmp); + return -1; + } + + return 0; +} + #else /* no PCRE or hivex at compile time */ /* XXX These functions should be in an optgroup. */ -- 1.7.3.2
Richard W.M. Jones
2010-Nov-15 14:21 UTC
[Libguestfs] [PATCH 2/3] New API: inspect-list-applications.
Convert the Perl code to C. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v -------------- next part -------------->From dda8950ab2e2450fb33a7164fbdbca270c4eb5f8 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Mon, 15 Nov 2010 12:26:36 +0000 Subject: [PATCH 2/3] New API: inspect-list-applications. This converts the current Perl code in virt-inspector for listing applications, into C, making it a part of the core API. --- generator/generator_actions.ml | 54 +++++ generator/generator_structs.ml | 9 + java/Makefile.inc | 1 + java/com/redhat/et/libguestfs/Application.java | 36 +++ src/guestfs.pod | 4 +- src/inspect.c | 279 ++++++++++++++++++++++++ 6 files changed, 382 insertions(+), 1 deletions(-) create mode 100644 java/com/redhat/et/libguestfs/Application.java diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml index c5bd44b..b6efb61 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -1169,6 +1169,60 @@ Future versions of libguestfs may return other strings. Please read L<guestfs(3)/INSPECTION> for more details."); + ("inspect_list_applications", (RStructList ("applications", "application"), [Device "root"], []), -1, [], + [], + "get list of applications installed in the operating system", + "\ +This function should only be called with a root device string +as returned by C<guestfs_inspect_os>. + +Return the list of applications installed in the operating system. + +I<Note:> This call works differently from other parts of the +inspection API. You have to call C<guestfs_inspect_os>, then +C<guestfs_inspect_get_mountpoints>, then mount up the disks, +before calling this. Listing applications is a significantly +more difficult operation which requires access to the full +filesystem. Also note that unlike the other +C<guestfs_inspect_get_*> calls which are just returning +data cached in the libguestfs handle, this call actually reads +parts of the mounted filesystems during the call. + +This returns an empty list if the inspection code was not able +to determine the list of applications. Support for Windows guests +is not implemented yet, so for Windows guests currently this function +always returns an empty list. We expect to support Windows in +a future version of libguestfs. + +The application structure contains the following fields: + +=over 4 + +=item C<app_name> + +The name of the application. For Red Hat-derived and Debian-derived +Linux guests, this is the package name. + +=item C<app_epoch> + +For package managers which use epochs, this contains the epoch of +the package (an integer). If unavailable, this returns C<0>. + +=item C<app_version> + +The version string of the application or package. If unavailable +this is returned as an empty string C<\"\">. + +=item C<app_release> + +The release string of the application or package, for package +managers that use this. If unavailable this is returned as an +empty string C<\"\">. + +=back + +Please read L<guestfs(3)/INSPECTION> for more details."); + ] (* daemon_functions are any functions which cause some action diff --git a/generator/generator_structs.ml b/generator/generator_structs.ml index 9cd585b..2dd4e99 100644 --- a/generator/generator_structs.ml +++ b/generator/generator_structs.ml @@ -175,6 +175,14 @@ let structs = [ "part_end", FBytes; "part_size", FBytes; ]; + + (* Application. *) + "application", [ + "app_name", FString; + "app_epoch", FInt32; + "app_version", FString; + "app_release", FString; + ]; ] (* end of structs *) (* Ugh, Java has to be different .. @@ -192,6 +200,7 @@ let java_structs = [ "xattr", "XAttr"; "inotify_event", "INotifyEvent"; "partition", "Partition"; + "application", "Application"; ] let java_name_of_struct typ diff --git a/java/Makefile.inc b/java/Makefile.inc index 88550ab..545f555 100644 --- a/java/Makefile.inc +++ b/java/Makefile.inc @@ -31,4 +31,5 @@ java_built_sources = \ com/redhat/et/libguestfs/XAttr.java \ com/redhat/et/libguestfs/INotifyEvent.java \ com/redhat/et/libguestfs/Partition.java \ + com/redhat/et/libguestfs/Application.java \ com/redhat/et/libguestfs/GuestFS.java diff --git a/java/com/redhat/et/libguestfs/Application.java b/java/com/redhat/et/libguestfs/Application.java new file mode 100644 index 0000000..dae3929 --- /dev/null +++ b/java/com/redhat/et/libguestfs/Application.java @@ -0,0 +1,36 @@ +/* libguestfs generated file + * WARNING: THIS FILE IS GENERATED FROM: + * generator/generator_*.ml + * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST. + * + * Copyright (C) 2009-2010 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 + */ + +package com.redhat.et.libguestfs; + +/** + * Libguestfs Application structure. + * + * @author rjones + * @see GuestFS + */ +public class Application { + public String app_name; + public int app_epoch; + public String app_version; + public String app_release; +} diff --git a/src/guestfs.pod b/src/guestfs.pod index b50f608..8370982 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -576,7 +576,9 @@ inspection and caches the results in the guest handle. Subsequent calls to C<guestfs_inspect_get_*> return this cached information, but I<do not> re-read the disks. If you change the content of the guest disks, you can redo inspection by calling L</guestfs_inspect_os> -again. +again. (L</guestfs_inspect_list_applications> works a little +differently from the other calls and does read the disks. See +documentation for that function for details). =head2 SPECIAL CONSIDERATIONS FOR WINDOWS GUESTS diff --git a/src/inspect.c b/src/inspect.c index 4533746..f5d6dda 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -1424,6 +1424,279 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root) return ret; } +static struct guestfs_application_list *list_applications_rpm (guestfs_h *g, struct inspect_fs *fs); +static struct guestfs_application_list *list_applications_deb (guestfs_h *g, struct inspect_fs *fs); +static void add_application (guestfs_h *g, struct guestfs_application_list *, const char *name, int32_t epoch, const char *version, const char *release); +static void sort_applications (struct guestfs_application_list *); + +/* Unlike the simple inspect-get-* calls, this one assumes that the + * disks are mounted up, and reads files from the mounted disks. + */ +struct guestfs_application_list * +guestfs__inspect_list_applications (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + struct guestfs_application_list *ret = NULL; + + switch (fs->package_format) { + case OS_PACKAGE_FORMAT_RPM: + ret = list_applications_rpm (g, fs); + if (ret == NULL) + return NULL; + break; + + case OS_PACKAGE_FORMAT_DEB: + ret = list_applications_deb (g, fs); + if (ret == NULL) + return NULL; + break; + + case OS_PACKAGE_FORMAT_UNKNOWN: + case OS_PACKAGE_FORMAT_PACMAN: + case OS_PACKAGE_FORMAT_EBUILD: + case OS_PACKAGE_FORMAT_PISI: + default: + /* nothing - keep GCC happy */; + } + + if (ret == NULL) { + /* Return an empty list. */ + ret = safe_malloc (g, sizeof *ret); + ret->len = 0; + ret->val = NULL; + } + + sort_applications (ret); + + return ret; +} + +static struct guestfs_application_list * +list_applications_rpm (guestfs_h *g, struct inspect_fs *fs) +{ + TMP_TEMPLATE_ON_STACK (tmpfile); + + if (download_to_tmp (g, "/var/lib/rpm/Name", tmpfile, 10000000) == -1) + return NULL; + + struct guestfs_application_list *apps = NULL, *ret = NULL; +#define cmd_len (strlen (tmpfile) + 64) + char cmd[cmd_len]; + FILE *pp = NULL; + char line[1024]; + size_t len; + + snprintf (cmd, cmd_len, "db_dump -p '%s'", tmpfile); + + if (g->verbose) + fprintf (stderr, "list_applications_rpm: %s\n", cmd); + + pp = popen (cmd, "r"); + if (pp == NULL) { + perrorf (g, "popen: %s", cmd); + goto out; + } + + /* Ignore everything to end-of-header marker. */ + for (;;) { + if (fgets (line, sizeof line, pp) == NULL) { + error (g, _("unexpected end of output from db_dump command")); + goto out; + } + + len = strlen (line); + if (len > 0 && line[len-1] == '\n') { + line[len-1] = '\0'; + len--; + } + + if (STREQ (line, "HEADER=END")) + break; + } + + /* Allocate 'apps' list. */ + apps = safe_malloc (g, sizeof *apps); + apps->len = 0; + apps->val = NULL; + + /* Read alternate lines until end of data marker. */ + for (;;) { + if (fgets (line, sizeof line, pp) == NULL) { + error (g, _("unexpected end of output from db_dump command")); + goto out; + } + + len = strlen (line); + if (len > 0 && line[len-1] == '\n') { + line[len-1] = '\0'; + len--; + } + + if (STREQ (line, "DATA=END")) + break; + + char *p = line; + if (len > 0 && line[0] == ' ') + p = line+1; + /* Ignore any application name that contains non-printable chars. + * In the db_dump output these would be escaped with backslash, so + * we can just ignore any such line. + */ + if (strchr (p, '\\') == NULL) + add_application (g, apps, p, 0, "", ""); + + /* Discard next line. */ + if (fgets (line, sizeof line, pp) == NULL) { + error (g, _("unexpected end of output from db_dump command")); + goto out; + } + } + + /* Catch errors from the db_dump command. */ + if (pclose (pp) == -1) { + perrorf (g, "pclose: %s", cmd); + goto out; + } + pp = NULL; + + ret = apps; + + out: + if (ret == NULL && apps != NULL) + guestfs_free_application_list (apps); + if (pp) + pclose (pp); + unlink (tmpfile); +#undef cmd_len + + return ret; +} + +static struct guestfs_application_list * +list_applications_deb (guestfs_h *g, struct inspect_fs *fs) +{ + TMP_TEMPLATE_ON_STACK (tmpfile); + + if (download_to_tmp (g, "/var/lib/dpkg/status", tmpfile, 10000000) == -1) + return NULL; + + struct guestfs_application_list *apps = NULL, *ret = NULL; + FILE *fp = NULL; + char line[1024]; + size_t len; + char *name = NULL, *version = NULL, *release = NULL; + int installed_flag = 0; + + fp = fopen (tmpfile, "r"); + if (fp == NULL) { + perrorf (g, "fopen: %s", tmpfile); + goto out; + } + + /* Allocate 'apps' list. */ + apps = safe_malloc (g, sizeof *apps); + apps->len = 0; + apps->val = NULL; + + /* Read the temporary file. Each package entry is separated by + * a blank line. + * XXX Strictly speaking this is in mailbox header format, so it + * would be possible for fields to spread across multiple lines, + * although for the short fields that we are concerned about this is + * unlikely and not seen in practice. + */ + while (fgets (line, sizeof line, fp) != NULL) { + len = strlen (line); + if (len > 0 && line[len-1] == '\n') { + line[len-1] = '\0'; + len--; + } + + if (STRPREFIX (line, "Package: ")) { + free (name); + name = safe_strdup (g, &line[9]); + } + else if (STRPREFIX (line, "Status: ")) { + installed_flag = strstr (&line[8], "installed") != NULL; + } + else if (STRPREFIX (line, "Version: ")) { + free (version); + free (release); + char *p = strchr (&line[9], '-'); + if (p) { + *p = '\0'; + version = safe_strdup (g, &line[9]); + release = safe_strdup (g, p+1); + } else { + version = safe_strdup (g, &line[9]); + release = NULL; + } + } + else if (STREQ (line, "")) { + if (installed_flag && name && version) { + add_application (g, apps, name, 0, version, release ? : ""); + free (name); + free (version); + free (release); + name = version = release = NULL; + installed_flag = 0; + } + } + } + + if (fclose (fp) == -1) { + perrorf (g, "fclose: %s", tmpfile); + goto out; + } + fp = NULL; + + ret = apps; + + out: + if (fp) + fclose (fp); + free (name); + free (version); + free (release); + unlink (tmpfile); + return ret; +} + +static void +add_application (guestfs_h *g, struct guestfs_application_list *apps, + const char *name, int32_t epoch, + const char *version, const char *release) +{ + apps->len++; + apps->val = safe_realloc (g, apps->val, + apps->len * sizeof (struct guestfs_application)); + apps->val[apps->len-1].app_name = safe_strdup (g, name); + apps->val[apps->len-1].app_epoch = epoch; + apps->val[apps->len-1].app_version = safe_strdup (g, version); + apps->val[apps->len-1].app_release = safe_strdup (g, release); +} + +/* Sort applications by name before returning the list. */ +static int +compare_applications (const void *vp1, const void *vp2) +{ + const struct guestfs_application *v1 = vp1; + const struct guestfs_application *v2 = vp2; + + return strcmp (v1->app_name, v2->app_name); +} + +static void +sort_applications (struct guestfs_application_list *apps) +{ + if (apps && apps->val) + qsort (apps->val, apps->len, sizeof (struct guestfs_application), + compare_applications); +} + /* Download to a guest file to a local temporary file. Refuse to * download the guest file if it is larger than max_size. The caller * is responsible for deleting the temporary file after use. @@ -1555,6 +1828,12 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root) NOT_IMPL(NULL); } +struct guestfs_application_list * +guestfs__inspect_list_applications (guestfs_h *g, const char *root) +{ + NOT_IMPL(NULL); +} + #endif /* no PCRE or hivex at compile time */ void -- 1.7.3.2
Richard W.M. Jones
2010-Nov-15 14:22 UTC
[Libguestfs] [PATCH 3/3] inspector: Replace code for listing applications with new core API.
-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://et.redhat.com/~rjones/virt-df/ -------------- next part -------------->From 2b46d516c9fcb326240f5885dca2d09d026fbfc3 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Mon, 15 Nov 2010 14:17:20 +0000 Subject: [PATCH 3/3] inspector: Replace code for listing applications with new core API. --- inspector/virt-inspector | 131 +++++------------------------------------ inspector/virt-inspector.rng | 1 + 2 files changed, 17 insertions(+), 115 deletions(-) diff --git a/inspector/virt-inspector b/inspector/virt-inspector index ce9ac0e..0acb727 100755 --- a/inspector/virt-inspector +++ b/inspector/virt-inspector @@ -189,6 +189,8 @@ $xml->startTag ("operatingsystems"); my $root; foreach $root (@roots) { + # Note that output_applications requires the filesystems + # to be mounted up. my %fses = $g->inspect_get_mountpoints ($root); my @fses = sort { length $a <=> length $b } keys %fses; foreach (@fses) { @@ -213,6 +215,10 @@ foreach $root (@roots) { $xml->dataElement (major_version => $s); $s = $g->inspect_get_minor_version ($root); $xml->dataElement (minor_version => $s); + $s = $g->inspect_get_package_format ($root); + $xml->dataElement (package_format => $s) if $s ne "unknown"; + $s = $g->inspect_get_package_management ($root); + $xml->dataElement (package_management => $s) if $s ne "unknown"; eval { $s = $g->inspect_get_windows_systemroot ($root); @@ -371,124 +377,19 @@ sub output_applications local $_; my $root = shift; - # Based on the distro, take a guess at the package format - # and package management. - my ($package_format, $package_management); - $package_format = $g->inspect_get_package_format ($root); - $package_management = $g->inspect_get_package_management ($root); + my @apps = $g->inspect_list_applications ($root); - $xml->dataElement (package_format => $package_format) - if $package_format ne "unknown"; - $xml->dataElement (package_management => $package_management) - if $package_management ne "unknown"; - - # Do we know how to get a list of applications? - if ($package_format eq "rpm") { - output_applications_rpm ($root); - } - elsif ($package_format eq "deb") { - output_applications_deb ($root); - } -} - -sub output_applications_rpm -{ - local $_; - my $root = shift; - - # Previous virt-inspector ran the 'rpm' program from the guest. - # This is insecure, and unnecessary because we can get the same - # information directly from the RPM database. - - my @applications; - - eval { - my ($fh, $filename) = tempfile (UNLINK => 1); - my $fddev = "/dev/fd/" . fileno ($fh); - $g->download ("/var/lib/rpm/Name", $fddev); - close $fh or die "close: $!"; - - # Read the database with the Berkeley DB dump tool. - my $cmd = "db_dump -p '$filename'"; - open PIPE, "$cmd |" or die "close: $!"; - while (<PIPE>) { - chomp; - last if /^HEADER=END$/; - } - while (<PIPE>) { - chomp; - last if /^DATA=END$/; - - # First character on each data line is a space. - if (length $_ > 0 && substr ($_, 0, 1) eq ' ') { - $_ = substr ($_, 1); - } - # Name should never contain non-printable chars. - die "name contains non-printable chars" if /\\/; - push @applications, $_; - - $_ = <PIPE>; # discard value - } - close PIPE or die "close: $!"; - }; - if (!$@ && @applications > 0) { - @applications = sort @applications; - $xml->startTag ("applications"); - foreach (@applications) { - $xml->startTag ("application"); - $xml->dataElement (name => $_); - $xml->endTag ("application"); - } - $xml->endTag ("applications"); - } -} - -sub output_applications_deb -{ - local $_; - my $root = shift; - - my @applications; - - eval { - my ($fh, $filename) = tempfile (UNLINK => 1); - my $fddev = "/dev/fd/" . fileno ($fh); - $g->download ("/var/lib/dpkg/status", $fddev); - close $fh or die "close: $!"; - - # Read the file. Each package is separated by a blank line. - open FILE, $filename or die "$filename: $!"; - my ($name, $installed, $version, $release); - while (<FILE>) { - chomp; - if (/^Package: (.*)/) { - $name = $1; - } elsif (/^Status: .*\binstalled\b/) { - $installed = 1; - } elsif (/^Version: (.*?)-(.*)/) { - $version = $1; - $release = $2; - } elsif ($_ eq "") { - if ($installed && - defined $name && defined $version && defined $release) { - push @applications, [ $name, $version, $release ]; - } - $name = undef; - $installed = undef; - $version = undef; - $release = undef; - } - } - close FILE or die "$filename: $!"; - }; - if (!$@ && @applications > 0) { - @applications = sort { $a->[0] cmp $b->[0] } @applications; + if (@apps) { $xml->startTag ("applications"); - foreach (@applications) { + foreach (@apps) { $xml->startTag ("application"); - $xml->dataElement (name => $_->[0]); - $xml->dataElement (version => $_->[1]); - $xml->dataElement (release => $_->[2]); + $xml->dataElement (name => $_->{app_name}); + $xml->dataElement (epoch => $_->{app_epoch}) + if $_->{app_epoch} != 0; + $xml->dataElement (version => $_->{app_version}) + if $_->{app_version} ne ""; + $xml->dataElement (release => $_->{app_release}) + if $_->{app_release} ne ""; $xml->endTag ("application"); } $xml->endTag ("applications"); diff --git a/inspector/virt-inspector.rng b/inspector/virt-inspector.rng index cd9d422..2d24102 100644 --- a/inspector/virt-inspector.rng +++ b/inspector/virt-inspector.rng @@ -84,6 +84,7 @@ <zeroOrMore> <element name="application"> <element name="name"><text/></element> + <optional><element name="epoch"><text/></element></optional> <optional><element name="version"><text/></element></optional> <optional><element name="release"><text/></element></optional> </element> -- 1.7.3.2
Reasonably Related Threads
- [PATCH 0/9] FOR DISCUSSION ONLY: daemon error handling
- [PATCH 0/9] Enhance virt-resize so it can really expand Linux and Windows guests
- [PATCH 0/6] Various Java bindings fixes.
- [PATCH febootstrap 0/8] Add support for building an ext2-based appliance
- [PATCH 0/5] Add new tools virt-tar and virt-ls and tidy up the tools code