diff options
author | Richard W.M. Jones <rjones@redhat.com> | 2011-06-27 15:27:46 +0100 |
---|---|---|
committer | Richard W.M. Jones <rjones@redhat.com> | 2011-06-28 18:41:54 +0100 |
commit | 7f16c346bbeba2f2fe3c31ccb85158178a284d84 (patch) | |
tree | 6d69f6b20598b05e8aa6eabeba96c40ade38f9f7 | |
parent | 5f26270c343bf543a7bf20cf3e6f182f6282f8ea (diff) | |
download | libguestfs-7f16c346bbeba2f2fe3c31ccb85158178a284d84.tar.gz libguestfs-7f16c346bbeba2f2fe3c31ccb85158178a284d84.tar.xz libguestfs-7f16c346bbeba2f2fe3c31ccb85158178a284d84.zip |
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.
-rw-r--r-- | TODO | 7 | ||||
-rw-r--r-- | generator/generator_actions.ml | 64 | ||||
-rw-r--r-- | inspector/virt-inspector.c | 32 | ||||
-rw-r--r-- | inspector/virt-inspector.rng | 6 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/guestfs-internal.h | 3 | ||||
-rw-r--r-- | src/inspect_icon.c | 470 |
8 files changed, 569 insertions, 15 deletions
@@ -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 ----------------------------------- diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml index fa2ff08b..d741dfb5 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -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 diff --git a/inspector/virt-inspector.c b/inspector/virt-inspector.c index 607e5ae0..4afce0e2 100644 --- a/inspector/virt-inspector.c +++ b/inspector/virt-inspector.c @@ -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")); diff --git a/inspector/virt-inspector.rng b/inspector/virt-inspector.rng index 480467ed..4fd208a3 100644 --- a/inspector/virt-inspector.rng +++ b/inspector/virt-inspector.rng @@ -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> diff --git a/po/POTFILES.in b/po/POTFILES.in index da47c913..c3f671a9 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index c2e0c6e5..6c0cc33d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 0650eb29..96f81525 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -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 diff --git a/src/inspect_icon.c b/src/inspect_icon.c new file mode 100644 index 00000000..ad0f62ce --- /dev/null +++ b/src/inspect_icon.c @@ -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; +} |