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.

This is also capable of fetching the list of Windows applications
from the registry.
This commit is contained in:
Richard W.M. Jones
2010-11-15 12:26:36 +00:00
parent 8a3a4c25d3
commit 163be3d72a
6 changed files with 573 additions and 1 deletions

1
.gitignore vendored
View File

@@ -140,6 +140,7 @@ install-sh
*.jar
java/api
java/Bindtests.java
java/com/redhat/et/libguestfs/Application.java
java/com/redhat/et/libguestfs/Dirent.java
java/com_redhat_et_libguestfs_GuestFS.c
java/com_redhat_et_libguestfs_GuestFS.h

View File

@@ -1175,6 +1175,105 @@ C<apt> (for all Debian derivatives),
C<portage>, C<pisi>, C<pacman>, C<urpmi>.
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.
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_display_name>
The display name of the application, sometimes localized to the
install language of the guest operating system.
If unavailable this is returned as an empty string C<\"\">.
Callers needing to display something can use C<app_name> instead.
=item C<app_epoch>
For package managers which use epochs, this contains the epoch of
the package (an integer). If unavailable, this is returned as 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<\"\">.
=item C<app_install_path>
The installation path of the application (on operating systems
such as Windows which use installation paths). This path is
in the format used by the guest operating system, it is not
a libguestfs path.
If unavailable this is returned as an empty string C<\"\">.
=item C<app_trans_path>
The install path translated into a libguestfs path.
If unavailable this is returned as an empty string C<\"\">.
=item C<app_publisher>
The name of the publisher of the application, for package
managers that use this. If unavailable this is returned
as an empty string C<\"\">.
=item C<app_url>
The URL (eg. upstream URL) of the application.
If unavailable this is returned as an empty string C<\"\">.
=item C<app_source_package>
For packaging systems which support this, the name of the source
package. If unavailable this is returned as an empty string C<\"\">.
=item C<app_summary>
A short (usually one line) description of the application or package.
If unavailable this is returned as an empty string C<\"\">.
=item C<app_description>
A longer description of the application or package.
If unavailable this is returned as an empty string C<\"\">.
=back
Please read L<guestfs(3)/INSPECTION> for more details.");
]

View File

@@ -175,6 +175,22 @@ let structs = [
"part_end", FBytes;
"part_size", FBytes;
];
(* Application. *)
"application", [
"app_name", FString;
"app_display_name", FString;
"app_epoch", FInt32;
"app_version", FString;
"app_release", FString;
"app_install_path", FString;
"app_trans_path", FString;
"app_publisher", FString;
"app_url", FString;
"app_source_package", FString;
"app_summary", FString;
"app_description", FString;
];
] (* end of structs *)
(* Ugh, Java has to be different ..
@@ -192,6 +208,7 @@ let java_structs = [
"xattr", "XAttr";
"inotify_event", "INotifyEvent";
"partition", "Partition";
"application", "Application";
]
let java_name_of_struct typ =

View File

@@ -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

View File

@@ -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

View File

@@ -1458,6 +1458,452 @@ 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 struct guestfs_application_list *list_applications_windows (guestfs_h *g, struct inspect_fs *fs);
static void add_application (guestfs_h *g, struct guestfs_application_list *, const char *name, const char *display_name, int32_t epoch, const char *version, const char *release, const char *install_path, const char *publisher, const char *url, const char *description);
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->type) {
case OS_TYPE_LINUX:
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_PACMAN:
case OS_PACKAGE_FORMAT_EBUILD:
case OS_PACKAGE_FORMAT_PISI:
case OS_PACKAGE_FORMAT_UNKNOWN:
default:
/* nothing - keep GCC happy */;
}
break;
case OS_TYPE_WINDOWS:
ret = list_applications_windows (g, fs);
if (ret == NULL)
return NULL;
break;
case OS_TYPE_FREEBSD:
case OS_TYPE_UNKNOWN:
default:
/* nothing - keep GCC happy */;
}
if (ret == NULL) {
/* Don't know how to do inspection. Not an error, 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 (ret == NULL && apps != NULL)
guestfs_free_application_list (apps);
if (fp)
fclose (fp);
free (name);
free (version);
free (release);
unlink (tmpfile);
return ret;
}
/* XXX We already download the SOFTWARE hive when doing general
* inspection. We could avoid this second download of the same file
* by caching these entries in the handle.
*/
static struct guestfs_application_list *
list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
{
TMP_TEMPLATE_ON_STACK (software_local);
size_t len = strlen (fs->windows_systemroot) + 64;
char software[len];
snprintf (software, len, "%s/system32/config/software",
fs->windows_systemroot);
char *software_path = resolve_windows_path_silently (g, software);
if (!software_path)
/* If the software hive doesn't exist, just accept that we cannot
* find product_name etc.
*/
return 0;
struct guestfs_application_list *apps = NULL, *ret = NULL;
hive_h *h = NULL;
hive_node_h *children = NULL;
if (download_to_tmp (g, software_path, software_local, 100000000) == -1)
goto out;
h = hivex_open (software_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
if (h == NULL) {
perrorf (g, "hivex_open");
goto out;
}
hive_node_h node = hivex_root (h);
const char *hivepath[] =
{ "Microsoft", "Windows", "CurrentVersion", "Uninstall" };
size_t i;
for (i = 0;
node != 0 && i < sizeof hivepath / sizeof hivepath[0];
++i) {
node = hivex_node_get_child (h, node, hivepath[i]);
}
if (node == 0) {
perrorf (g, "hivex: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
goto out;
}
children = hivex_node_children (h, node);
if (children == NULL) {
perrorf (g, "hivex_node_children");
goto out;
}
/* Allocate 'apps' list. */
apps = safe_malloc (g, sizeof *apps);
apps->len = 0;
apps->val = NULL;
/* Consider any child node that has a DisplayName key.
* See also:
* http://nsis.sourceforge.net/Add_uninstall_information_to_Add/Remove_Programs#Optional_values
*/
for (i = 0; children[i] != 0; ++i) {
hive_value_h value;
char *name = NULL;
char *display_name = NULL;
char *version = NULL;
char *install_path = NULL;
char *publisher = NULL;
char *url = NULL;
char *comments = NULL;
/* Use the node name as a proxy for the package name in Linux. The
* display name is not language-independent, so it cannot be used.
*/
name = hivex_node_name (h, children[i]);
if (name == NULL) {
perrorf (g, "hivex_node_get_name");
goto out;
}
value = hivex_node_get_value (h, children[i], "DisplayName");
if (value) {
display_name = hivex_value_string (h, value);
if (display_name) {
value = hivex_node_get_value (h, children[i], "DisplayVersion");
if (value)
version = hivex_value_string (h, value);
value = hivex_node_get_value (h, children[i], "InstallLocation");
if (value)
install_path = hivex_value_string (h, value);
value = hivex_node_get_value (h, children[i], "Publisher");
if (value)
publisher = hivex_value_string (h, value);
value = hivex_node_get_value (h, children[i], "URLInfoAbout");
if (value)
url = hivex_value_string (h, value);
value = hivex_node_get_value (h, children[i], "Comments");
if (value)
comments = hivex_value_string (h, value);
add_application (g, apps, name, display_name, 0,
version ? : "",
"",
install_path ? : "",
publisher ? : "",
url ? : "",
comments ? : "");
}
}
free (name);
free (display_name);
free (version);
free (install_path);
free (publisher);
free (url);
free (comments);
}
ret = apps;
out:
if (ret == NULL && apps != NULL)
guestfs_free_application_list (apps);
if (h) hivex_close (h);
free (children);
free (software_path);
/* Free up the temporary file. */
unlink (software_local);
#undef software_local_len
return ret;
}
static void
add_application (guestfs_h *g, struct guestfs_application_list *apps,
const char *name, const char *display_name, int32_t epoch,
const char *version, const char *release,
const char *install_path,
const char *publisher, const char *url,
const char *description)
{
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_display_name = safe_strdup (g, display_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);
apps->val[apps->len-1].app_install_path = safe_strdup (g, install_path);
/* XXX Translated path is not implemented yet. */
apps->val[apps->len-1].app_trans_path = safe_strdup (g, "");
apps->val[apps->len-1].app_publisher = safe_strdup (g, publisher);
apps->val[apps->len-1].app_url = safe_strdup (g, url);
/* XXX The next two are not yet implemented for any package
* format, but we could easily support them for rpm and deb.
*/
apps->val[apps->len-1].app_source_package = safe_strdup (g, "");
apps->val[apps->len-1].app_summary = safe_strdup (g, "");
apps->val[apps->len-1].app_description = safe_strdup (g, description);
}
/* 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.
@@ -1589,6 +2035,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