New API: inspect-get-icon returns the guest icon.

This API returns the guest's favicon if found, else an icon
representing the guest operating system.  Currently supported by this
patch: Fedora, RHEL and derivatives, Debian (but not Ubuntu),
Windows XP, Windows 7.

This also updates virt-inspector to include an <icon> element
containing the icon in base64 encoding.
This commit is contained in:
Richard W.M. Jones
2011-06-27 15:27:46 +01:00
parent 5f26270c34
commit 7f16c346bb
8 changed files with 569 additions and 15 deletions

7
TODO
View File

@@ -441,13 +441,6 @@ More inspection features
- last user who logged in
- lastlog, last, who
Get the guest icon
------------------
- For Linux guests, use /etc/favicon.png if available, else get it in
a distro-specific manner.
- For Windows guests, parse it out of c:\windows\explorer.exe
Integrate virt-inspector with CMDBs
-----------------------------------

View File

@@ -1462,6 +1462,70 @@ Please read L<guestfs(3)/INSPECTION> for more details.
See also C<guestfs_inspect_get_mountpoints>,
C<guestfs_inspect_get_filesystems>.");
("inspect_get_icon", (RBufferOut "icon", [Device "root"], [Bool "favicon"; Bool "highquality"]), -1, [],
[],
"get the icon corresponding to this operating system",
"\
This function returns an icon corresponding to the inspected
operating system. The icon is returned as a buffer containing a
PNG image (re-encoded to PNG if necessary).
If it was not possible to get an icon this function returns a
zero-length (non-NULL) buffer. I<Callers must check for this case>.
Libguestfs will start by looking for a file called
C</etc/favicon.png> or C<C:\\etc\\favicon.png>
and if it has the correct format, the contents of this file will
be returned. You can disable favicons by passing the
optional C<favicon> boolean as false (default is true).
If finding the favicon fails, then we look in other places in the
guest for a suitable icon.
If the optional C<highquality> boolean is true then
only high quality icons are returned, which means only icons of
high resolution with an alpha channel. The default (false) is
to return any icon we can, even if it is of substandard quality.
Notes:
=over 4
=item *
Unlike most other inspection API calls, the guest's disks must be
mounted up before you call this, since it needs to read information
from the guest filesystem during the call.
=item *
B<Security:> The icon data comes from the untrusted guest,
and should be treated with caution. PNG files have been
known to contain exploits. Ensure that libpng (or other relevant
libraries) are fully up to date before trying to process or
display the icon.
=item *
The PNG image returned can be any size. It might not be square.
Libguestfs tries to return the largest, highest quality
icon available. The application must scale the icon to the
required size.
=item *
Extracting icons from Windows guests requires the external
C<wrestool> program from the C<icoutils> package, and
several programs (C<bmptopnm>, C<pnmtopng>, C<pamcut>)
from the C<netpbm> package. These must be installed separately.
=item *
Operating system icons are usually trademarks. Seek legal
advice before using trademarks in applications.
=back");
]
(* daemon_functions are any functions which cause some action

View File

@@ -340,6 +340,7 @@ output_root (xmlTextWriterPtr xo, char *root)
int i, r;
char buf[32];
char canonical_root[strlen (root) + 1];
size_t size;
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystem"));
@@ -471,8 +472,32 @@ output_root (xmlTextWriterPtr xo, char *root)
output_drive_mappings (xo, root);
/* We need to mount everything up in order to read out the list of
* applications and the icon, ie. everything below this point.
*/
inspect_mount_root (root);
output_applications (xo, root);
/* Don't return favicon. XXX Should we? */
str = guestfs_inspect_get_icon (g, root, &size,
GUESTFS_INSPECT_GET_ICON_FAVICON, 0,
-1);
if (!str) exit (EXIT_FAILURE);
if (size > 0) {
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "icon"));
XMLERROR (-1, xmlTextWriterWriteBase64 (xo, str, 0, size));
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
/* Note we must free (str) even if size == 0, because that indicates
* there was no icon.
*/
free (str);
/* Unmount (see inspect_mount_root above). */
if (guestfs_umount_all (g) == -1)
exit (EXIT_FAILURE);
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
@@ -652,19 +677,12 @@ output_applications (xmlTextWriterPtr xo, char *root)
struct guestfs_application_list *apps;
size_t i;
/* We need to mount everything up in order to read out the list of
* applications.
*/
inspect_mount_root (root);
/* This returns an empty list if we simply couldn't determine the
* applications, so if it returns NULL then it's a real error.
*/
apps = guestfs_inspect_list_applications (g, root);
if (apps == NULL)
exit (EXIT_FAILURE);
if (guestfs_umount_all (g) == -1)
exit (EXIT_FAILURE);
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "applications"));

View File

@@ -1,4 +1,6 @@
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<grammar
xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<!-- -*- xml -*-
This is a RELAX NG schema for the output of 'virt-inspector'.
@@ -51,6 +53,8 @@
<optional><ref name="drive_mappings"/></optional>
<optional><ref name="applications"/></optional>
<optional><element name="icon"><data type="base64Binary"/></element></optional>
</interleave>
</element>
</oneOrMore>

View File

@@ -147,6 +147,7 @@ src/inspect_fs.c
src/inspect_fs_cd.c
src/inspect_fs_unix.c
src/inspect_fs_windows.c
src/inspect_icon.c
src/launch.c
src/listfs.c
src/match.c

View File

@@ -133,6 +133,7 @@ libguestfs_la_SOURCES = \
inspect_fs_cd.c \
inspect_fs_unix.c \
inspect_fs_windows.c \
inspect_icon.c \
launch.c \
listfs.c \
match.c \

View File

@@ -89,6 +89,9 @@
*/
#define MAX_PKG_DB_SIZE (300 * 1000 * 1000)
/* Maximum size of Windows explorer.exe. 2.6MB on Windows 7. */
#define MAX_WINDOWS_EXPLORER_SIZE (4 * 1000 * 1000)
/* Network configuration of the appliance. Note these addresses are
* only meaningful within the context of the running appliance. QEMU
* translates network connections to these magic addresses into

470
src/inspect_icon.c Normal file
View File

@@ -0,0 +1,470 @@
/* libguestfs
* Copyright (C) 2011 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
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include "guestfs.h"
#include "guestfs-internal.h"
#include "guestfs-internal-actions.h"
#include "guestfs_protocol.h"
static int read_whole_file (guestfs_h *g, const char *filename, char **data_r, size_t *size_r);
/* All these icon_*() functions return the same way. One of:
*
* ret == NULL:
* An error occurred. Error has been set in the handle. The caller
* should return NULL immediately.
*
* ret == NOT_FOUND:
* Not an error, but no icon was found. 'ret' is just a dummy value
* which should be ignored (do not free it!)
*
* ret == ordinary pointer:
* An icon was found. 'ret' points to the icon buffer, and *size_r
* is the size.
*/
static char *icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
static char *icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
static char *icon_rhel (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
static char *icon_debian (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
static char *icon_windows (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
/* Dummy static object. */
static char *NOT_FOUND = (char *) "not_found";
/* For the unexpected legal consequences of this function, see:
* http://lists.fedoraproject.org/pipermail/legal/2011-April/001615.html
*
* Returns an RBufferOut, so the length of the returned buffer is
* returned in *size_r.
*
* Check optargs for the optional argument.
*/
char *
guestfs__inspect_get_icon (guestfs_h *g, const char *root, size_t *size_r,
const struct guestfs_inspect_get_icon_argv *optargs)
{
struct inspect_fs *fs;
char *r = NOT_FOUND;
int favicon, highquality;
size_t size;
fs = guestfs___search_for_root (g, root);
if (!fs)
return NULL;
/* Get optargs, or defaults. */
favicon =
optargs->bitmask & GUESTFS_INSPECT_GET_ICON_FAVICON_BITMASK ?
optargs->favicon : 1;
highquality =
optargs->bitmask & GUESTFS_INSPECT_GET_ICON_HIGHQUALITY_BITMASK ?
optargs->highquality : 0;
/* Favicons are never high quality, so ... */
if (highquality)
favicon = 0;
/* Try looking for a favicon first. */
if (favicon) {
r = icon_favicon (g, fs, &size);
if (!r)
return NULL;
if (r != NOT_FOUND) {
/* try_favicon succeeded in finding a favicon. */
*size_r = size;
return r;
}
}
/* Favicon failed, so let's try a method based on the detected operating
* system.
*/
switch (fs->type) {
case OS_TYPE_LINUX:
switch (fs->distro) {
case OS_DISTRO_FEDORA:
r = icon_fedora (g, fs, &size);
break;
case OS_DISTRO_RHEL:
case OS_DISTRO_REDHAT_BASED:
case OS_DISTRO_CENTOS:
case OS_DISTRO_SCIENTIFIC_LINUX:
r = icon_rhel (g, fs, &size);
break;
case OS_DISTRO_DEBIAN:
r = icon_debian (g, fs, &size);
break;
/* These are just to keep gcc warnings happy. */
case OS_DISTRO_ARCHLINUX:
case OS_DISTRO_GENTOO:
case OS_DISTRO_LINUX_MINT:
case OS_DISTRO_MANDRIVA:
case OS_DISTRO_MEEGO:
case OS_DISTRO_PARDUS:
case OS_DISTRO_SLACKWARE:
case OS_DISTRO_UBUNTU:
case OS_DISTRO_WINDOWS:
case OS_DISTRO_UNKNOWN:
default: ;
}
break;
case OS_TYPE_WINDOWS:
/* We don't know how to get high quality icons from a Windows guest,
* so disable this if high quality was specified.
*/
if (!highquality)
r = icon_windows (g, fs, &size);
break;
case OS_TYPE_FREEBSD:
case OS_TYPE_UNKNOWN:
default: ;
}
if (r == NOT_FOUND) {
/* Not found, but not an error. So return the special zero-length
* buffer. Use malloc(1) here to ensure that malloc won't return
* NULL.
*/
r = safe_malloc (g, 1);
size = 0;
}
*size_r = size;
return r;
}
/* Check that the named file 'filename' is a PNG file and is reasonable.
* If it is, download and return it.
*/
static char *
get_png (guestfs_h *g, struct inspect_fs *fs, const char *filename,
size_t *size_r, size_t max_size)
{
char *ret = NOT_FOUND;
char *type = NULL;
char *local = NULL;
int r, w, h;
r = guestfs_exists (g, filename);
if (r == -1) {
ret = NULL; /* a real error */
goto out;
}
if (r == 0) goto out;
/* Check the file type and geometry. */
type = guestfs_file (g, filename);
if (!type) goto out;
if (!STRPREFIX (type, "PNG image data, ")) goto out;
if (sscanf (&type[16], "%d x %d", &w, &h) != 2) goto out;
if (w < 16 || h < 16 || w > 1024 || h > 1024) goto out;
/* Define a maximum reasonable size based on the geometry. This
* also limits the maximum we allocate below to around 4 MB.
*/
if (max_size == 0)
max_size = 4 * w * h;
local = guestfs___download_to_tmp (g, fs, filename, "icon", max_size);
if (!local) goto out;
/* Successfully passed checks and downloaded. Read it into memory. */
if (read_whole_file (g, local, &ret, size_r) == -1) {
ret = NULL;
goto out;
}
out:
free (local);
free (type);
return ret;
}
/* Return /etc/favicon.png (or \etc\favicon.png) if it exists and if
* it has a reasonable size and format.
*/
static char *
icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
{
char *ret;
char *filename = safe_strdup (g, "/etc/favicon.png");
if (fs->type == OS_TYPE_WINDOWS) {
char *f = guestfs___case_sensitive_path_silently (g, filename);
if (f) {
free (filename);
filename = f;
}
}
ret = get_png (g, fs, filename, size_r, 0);
free (filename);
return ret;
}
/* Return FEDORA_ICON. I checked that this exists on at least Fedora 6
* through 16.
*/
#define FEDORA_ICON "/usr/share/icons/hicolor/96x96/apps/fedora-logo-icon.png"
static char *
icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
{
return get_png (g, fs, FEDORA_ICON, size_r, 0);
}
/* RHEL 3, 4:
* /usr/share/pixmaps/redhat/shadowman-transparent.png is a 517x515
* PNG with alpha channel, around 64K in size.
*
* RHEL 5, 6:
* As above, but the file has been optimized to about 16K.
*
* Conveniently the RHEL clones also have the same file with the
* same name, but containing their own logos. Sense prevails!
*/
#define SHADOWMAN_ICON "/usr/share/pixmaps/redhat/shadowman-transparent.png"
static char *
icon_rhel (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
{
size_t max_size = 0;
if (fs->distro == OS_DISTRO_RHEL) {
if (fs->major_version <= 4)
max_size = 66000;
else
max_size = 17000;
}
return get_png (g, fs, SHADOWMAN_ICON, size_r, max_size);
}
/* NB: I've not located an Ubuntu logo yet. */
#define DEBIAN_ICON "/usr/share/pixmaps/debian-logo.png"
static char *
icon_debian (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
{
return get_png (g, fs, DEBIAN_ICON, size_r, 2048);
}
/* Windows, as usual, has to be much more complicated and stupid than
* anything else.
*
* We have to download %systemroot%\explorer.exe and use a special
* program called 'wrestool' to extract the icons from this file. For
* each version of Windows, the icon we want is in a different place.
* The icon is in a stupid format (BMP), and in some cases multiple
* icons are in a single BMP file so we have to do some manipulation
* on the file.
*
* XXX I've only bothered with this nonsense for a few versions of
* Windows that I have handy. Please send patches to support other
* versions.
*/
static char *
icon_windows_xp (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
size_t *size_r)
{
char *ret;
char *pngfile;
char *cmd;
int r;
pngfile = safe_asprintf (g, "%s/windows-xp-icon.png", g->tmpdir);
cmd = safe_asprintf (g,
"wrestool -x --type=2 --name=143 %s | "
"bmptopnm | pnmtopng > %s",
explorer, pngfile);
r = system (cmd);
if (r == -1 || WEXITSTATUS (r) != 0) {
debug (g, "external command failed: %s", cmd);
free (cmd);
free (pngfile);
return NOT_FOUND;
}
free (cmd);
if (read_whole_file (g, pngfile, &ret, size_r) == -1) {
free (pngfile);
return NULL;
}
free (pngfile);
return ret;
}
static char *
icon_windows_7 (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
size_t *size_r)
{
char *ret;
char *pngfile;
char *cmd;
int r;
pngfile = safe_asprintf (g, "%s/windows-7-icon.png", g->tmpdir);
cmd = safe_asprintf (g,
"wrestool -x --type=2 --name=6801 %s | "
"bmptopnm | pamcut -bottom 54 | pnmtopng > %s",
explorer, pngfile);
r = system (cmd);
if (r == -1 || WEXITSTATUS (r) != 0) {
debug (g, "external command failed: %s", cmd);
free (cmd);
free (pngfile);
return NOT_FOUND;
}
free (cmd);
if (read_whole_file (g, pngfile, &ret, size_r) == -1) {
free (pngfile);
return NULL;
}
free (pngfile);
return ret;
}
static char *
icon_windows (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
{
char *(*fn) (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
size_t *size_r);
char *filename1, *filename2, *filename3;
char *ret;
/* Windows XP. */
if (fs->major_version == 5 && fs->minor_version == 1)
fn = icon_windows_xp;
/* Windows 7 */
else if (fs->major_version == 6 && fs->minor_version == 1)
fn = icon_windows_7;
/* Not (yet) a supported version of Windows. */
else return NOT_FOUND;
if (fs->windows_systemroot == NULL)
return NOT_FOUND;
/* Download %systemroot%\explorer.exe */
filename1 = safe_asprintf (g, "%s/explorer.exe", fs->windows_systemroot);
filename2 = guestfs___case_sensitive_path_silently (g, filename1);
free (filename1);
if (filename2 == NULL)
return NOT_FOUND;
filename3 = guestfs___download_to_tmp (g, fs, filename2, "explorer",
MAX_WINDOWS_EXPLORER_SIZE);
free (filename2);
if (filename3 == NULL)
return NOT_FOUND;
ret = fn (g, fs, filename3, size_r);
free (filename3);
return ret;
}
/* Read the whole file into a memory buffer and return it. The file
* should be a regular, local, trusted file.
*/
static int
read_whole_file (guestfs_h *g, const char *filename,
char **data_r, size_t *size_r)
{
int fd;
char *data;
off_t size;
off_t n;
ssize_t r;
struct stat statbuf;
fd = open (filename, O_RDONLY);
if (fd == -1) {
perrorf (g, "open: %s", filename);
return -1;
}
if (fstat (fd, &statbuf) == -1) {
perrorf (g, "stat: %s", filename);
close (fd);
return -1;
}
size = statbuf.st_size;
data = safe_malloc (g, size);
n = 0;
while (n < size) {
r = read (fd, &data[n], size - n);
if (r == -1) {
perrorf (g, "read: %s", filename);
free (data);
close (fd);
return -1;
}
if (r == 0) {
error (g, _("read: %s: unexpected end of file"), filename);
free (data);
close (fd);
return -1;
}
n += r;
}
if (close (fd) == -1) {
perrorf (g, "close: %s", filename);
free (data);
return -1;
}
*data_r = data;
*size_r = size;
return 0;
}