Require PCRE library.
[libguestfs.git] / src / inspect_icon.c
1 /* libguestfs
2  * Copyright (C) 2011 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <inttypes.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30
31 #include "guestfs.h"
32 #include "guestfs-internal.h"
33 #include "guestfs-internal-actions.h"
34 #include "guestfs_protocol.h"
35
36 static int read_whole_file (guestfs_h *g, const char *filename, char **data_r, size_t *size_r);
37
38 /* All these icon_*() functions return the same way.  One of:
39  *
40  *   ret == NULL:
41  *     An error occurred.  Error has been set in the handle.  The caller
42  *     should return NULL immediately.
43  *
44  *   ret == NOT_FOUND:
45  *     Not an error, but no icon was found.  'ret' is just a dummy value
46  *     which should be ignored (do not free it!)
47  *
48  *   ret == ordinary pointer:
49  *     An icon was found.  'ret' points to the icon buffer, and *size_r
50  *     is the size.
51  */
52 static char *icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
53 static char *icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
54 static char *icon_rhel (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
55 static char *icon_debian (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
56 static char *icon_windows (guestfs_h *g, struct inspect_fs *fs, size_t *size_r);
57
58 /* Dummy static object. */
59 static char *NOT_FOUND = (char *) "not_found";
60
61 /* For the unexpected legal consequences of this function, see:
62  * http://lists.fedoraproject.org/pipermail/legal/2011-April/001615.html
63  *
64  * Returns an RBufferOut, so the length of the returned buffer is
65  * returned in *size_r.
66  *
67  * Check optargs for the optional argument.
68  */
69 char *
70 guestfs__inspect_get_icon (guestfs_h *g, const char *root, size_t *size_r,
71                            const struct guestfs_inspect_get_icon_argv *optargs)
72 {
73   struct inspect_fs *fs;
74   char *r = NOT_FOUND;
75   int favicon, highquality;
76   size_t size;
77
78   fs = guestfs___search_for_root (g, root);
79   if (!fs)
80     return NULL;
81
82   /* Get optargs, or defaults. */
83   favicon =
84     optargs->bitmask & GUESTFS_INSPECT_GET_ICON_FAVICON_BITMASK ?
85     optargs->favicon : 1;
86
87   highquality =
88     optargs->bitmask & GUESTFS_INSPECT_GET_ICON_HIGHQUALITY_BITMASK ?
89     optargs->highquality : 0;
90
91   /* Favicons are never high quality, so ... */
92   if (highquality)
93     favicon = 0;
94
95   /* Try looking for a favicon first. */
96   if (favicon) {
97     r = icon_favicon (g, fs, &size);
98     if (!r)
99       return NULL;
100
101     if (r != NOT_FOUND) {
102       /* try_favicon succeeded in finding a favicon. */
103       *size_r = size;
104       return r;
105     }
106   }
107
108   /* Favicon failed, so let's try a method based on the detected operating
109    * system.
110    */
111   switch (fs->type) {
112   case OS_TYPE_LINUX:
113     switch (fs->distro) {
114     case OS_DISTRO_FEDORA:
115       r = icon_fedora (g, fs, &size);
116       break;
117
118     case OS_DISTRO_RHEL:
119     case OS_DISTRO_REDHAT_BASED:
120     case OS_DISTRO_CENTOS:
121     case OS_DISTRO_SCIENTIFIC_LINUX:
122       r = icon_rhel (g, fs, &size);
123       break;
124
125     case OS_DISTRO_DEBIAN:
126       r = icon_debian (g, fs, &size);
127       break;
128
129       /* These are just to keep gcc warnings happy. */
130     case OS_DISTRO_ARCHLINUX:
131     case OS_DISTRO_GENTOO:
132     case OS_DISTRO_LINUX_MINT:
133     case OS_DISTRO_MANDRIVA:
134     case OS_DISTRO_MEEGO:
135     case OS_DISTRO_PARDUS:
136     case OS_DISTRO_SLACKWARE:
137     case OS_DISTRO_UBUNTU:
138     case OS_DISTRO_WINDOWS:
139     case OS_DISTRO_UNKNOWN:
140     default: ;
141     }
142     break;
143
144   case OS_TYPE_WINDOWS:
145     /* We don't know how to get high quality icons from a Windows guest,
146      * so disable this if high quality was specified.
147      */
148     if (!highquality)
149       r = icon_windows (g, fs, &size);
150     break;
151
152   case OS_TYPE_FREEBSD:
153   case OS_TYPE_UNKNOWN:
154   default: ;
155   }
156
157   if (r == NOT_FOUND) {
158     /* Not found, but not an error.  So return the special zero-length
159      * buffer.  Use malloc(1) here to ensure that malloc won't return
160      * NULL.
161      */
162     r = safe_malloc (g, 1);
163     size = 0;
164   }
165
166   *size_r = size;
167   return r;
168 }
169
170 /* Check that the named file 'filename' is a PNG file and is reasonable.
171  * If it is, download and return it.
172  */
173 static char *
174 get_png (guestfs_h *g, struct inspect_fs *fs, const char *filename,
175          size_t *size_r, size_t max_size)
176 {
177   char *ret = NOT_FOUND;
178   char *type = NULL;
179   char *local = NULL;
180   int r, w, h;
181
182   r = guestfs_exists (g, filename);
183   if (r == -1) {
184     ret = NULL; /* a real error */
185     goto out;
186   }
187   if (r == 0) goto out;
188
189   /* Check the file type and geometry. */
190   type = guestfs_file (g, filename);
191   if (!type) goto out;
192
193   if (!STRPREFIX (type, "PNG image data, ")) goto out;
194   if (sscanf (&type[16], "%d x %d", &w, &h) != 2) goto out;
195   if (w < 16 || h < 16 || w > 1024 || h > 1024) goto out;
196
197   /* Define a maximum reasonable size based on the geometry.  This
198    * also limits the maximum we allocate below to around 4 MB.
199    */
200   if (max_size == 0)
201     max_size = 4 * w * h;
202
203   local = guestfs___download_to_tmp (g, fs, filename, "icon", max_size);
204   if (!local) goto out;
205
206   /* Successfully passed checks and downloaded.  Read it into memory. */
207   if (read_whole_file (g, local, &ret, size_r) == -1) {
208     ret = NULL;
209     goto out;
210   }
211
212  out:
213   free (local);
214   free (type);
215
216   return ret;
217 }
218
219 /* Return /etc/favicon.png (or \etc\favicon.png) if it exists and if
220  * it has a reasonable size and format.
221  */
222 static char *
223 icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
224 {
225   char *ret;
226   char *filename = safe_strdup (g, "/etc/favicon.png");
227
228   if (fs->type == OS_TYPE_WINDOWS) {
229     char *f = guestfs___case_sensitive_path_silently (g, filename);
230     if (f) {
231       free (filename);
232       filename = f;
233     }
234   }
235
236   ret = get_png (g, fs, filename, size_r, 0);
237   free (filename);
238   return ret;
239 }
240
241 /* Return FEDORA_ICON.  I checked that this exists on at least Fedora 6
242  * through 16.
243  */
244 #define FEDORA_ICON "/usr/share/icons/hicolor/96x96/apps/fedora-logo-icon.png"
245
246 static char *
247 icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
248 {
249   return get_png (g, fs, FEDORA_ICON, size_r, 0);
250 }
251
252 /* RHEL 3, 4:
253  * /usr/share/pixmaps/redhat/shadowman-transparent.png is a 517x515
254  * PNG with alpha channel, around 64K in size.
255  *
256  * RHEL 5, 6:
257  * As above, but the file has been optimized to about 16K.
258  *
259  * Conveniently the RHEL clones also have the same file with the
260  * same name, but containing their own logos.  Sense prevails!
261  */
262 #define SHADOWMAN_ICON "/usr/share/pixmaps/redhat/shadowman-transparent.png"
263
264 static char *
265 icon_rhel (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
266 {
267   size_t max_size = 0;
268
269   if (fs->distro == OS_DISTRO_RHEL) {
270     if (fs->major_version <= 4)
271       max_size = 66000;
272     else
273       max_size = 17000;
274   }
275
276   return get_png (g, fs, SHADOWMAN_ICON, size_r, max_size);
277 }
278
279 /* NB: I've not located an Ubuntu logo yet. */
280 #define DEBIAN_ICON "/usr/share/pixmaps/debian-logo.png"
281
282 static char *
283 icon_debian (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
284 {
285   return get_png (g, fs, DEBIAN_ICON, size_r, 2048);
286 }
287
288 /* Windows, as usual, has to be much more complicated and stupid than
289  * anything else.
290  *
291  * We have to download %systemroot%\explorer.exe and use a special
292  * program called 'wrestool' to extract the icons from this file.  For
293  * each version of Windows, the icon we want is in a different place.
294  * The icon is in a stupid format (BMP), and in some cases multiple
295  * icons are in a single BMP file so we have to do some manipulation
296  * on the file.
297  *
298  * XXX I've only bothered with this nonsense for a few versions of
299  * Windows that I have handy.  Please send patches to support other
300  * versions.
301  */
302
303 static char *
304 icon_windows_xp (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
305                  size_t *size_r)
306 {
307   char *ret;
308   char *pngfile;
309   char *cmd;
310   int r;
311
312   pngfile = safe_asprintf (g, "%s/windows-xp-icon.png", g->tmpdir);
313
314   cmd = safe_asprintf (g,
315                        "wrestool -x --type=2 --name=143 %s | "
316                        "bmptopnm | pnmtopng > %s",
317           explorer, pngfile);
318   r = system (cmd);
319   if (r == -1 || WEXITSTATUS (r) != 0) {
320     debug (g, "external command failed: %s", cmd);
321     free (cmd);
322     free (pngfile);
323     return NOT_FOUND;
324   }
325
326   free (cmd);
327
328   if (read_whole_file (g, pngfile, &ret, size_r) == -1) {
329     free (pngfile);
330     return NULL;
331   }
332
333   free (pngfile);
334
335   return ret;
336 }
337
338 static char *
339 icon_windows_7 (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
340                 size_t *size_r)
341 {
342   char *ret;
343   char *pngfile;
344   char *cmd;
345   int r;
346
347   pngfile = safe_asprintf (g, "%s/windows-7-icon.png", g->tmpdir);
348
349   cmd = safe_asprintf (g,
350                        "wrestool -x --type=2 --name=6801 %s | "
351                        "bmptopnm | pamcut -bottom 54 | pnmtopng > %s",
352           explorer, pngfile);
353   r = system (cmd);
354   if (r == -1 || WEXITSTATUS (r) != 0) {
355     debug (g, "external command failed: %s", cmd);
356     free (cmd);
357     free (pngfile);
358     return NOT_FOUND;
359   }
360
361   free (cmd);
362
363   if (read_whole_file (g, pngfile, &ret, size_r) == -1) {
364     free (pngfile);
365     return NULL;
366   }
367
368   free (pngfile);
369
370   return ret;
371 }
372
373 static char *
374 icon_windows (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
375 {
376   char *(*fn) (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
377                size_t *size_r);
378   char *filename1, *filename2, *filename3;
379   char *ret;
380
381   /* Windows XP. */
382   if (fs->major_version == 5 && fs->minor_version == 1)
383     fn = icon_windows_xp;
384
385   /* Windows 7 */
386   else if (fs->major_version == 6 && fs->minor_version == 1)
387     fn = icon_windows_7;
388
389   /* Not (yet) a supported version of Windows. */
390   else return NOT_FOUND;
391
392   if (fs->windows_systemroot == NULL)
393     return NOT_FOUND;
394
395   /* Download %systemroot%\explorer.exe */
396   filename1 = safe_asprintf (g, "%s/explorer.exe", fs->windows_systemroot);
397   filename2 = guestfs___case_sensitive_path_silently (g, filename1);
398   free (filename1);
399   if (filename2 == NULL)
400     return NOT_FOUND;
401
402   filename3 = guestfs___download_to_tmp (g, fs, filename2, "explorer",
403                                          MAX_WINDOWS_EXPLORER_SIZE);
404   free (filename2);
405   if (filename3 == NULL)
406     return NOT_FOUND;
407
408   ret = fn (g, fs, filename3, size_r);
409   free (filename3);
410   return ret;
411 }
412
413 /* Read the whole file into a memory buffer and return it.  The file
414  * should be a regular, local, trusted file.
415  */
416 static int
417 read_whole_file (guestfs_h *g, const char *filename,
418                  char **data_r, size_t *size_r)
419 {
420   int fd;
421   char *data;
422   off_t size;
423   off_t n;
424   ssize_t r;
425   struct stat statbuf;
426
427   fd = open (filename, O_RDONLY);
428   if (fd == -1) {
429     perrorf (g, "open: %s", filename);
430     return -1;
431   }
432
433   if (fstat (fd, &statbuf) == -1) {
434     perrorf (g, "stat: %s", filename);
435     close (fd);
436     return -1;
437   }
438
439   size = statbuf.st_size;
440   data = safe_malloc (g, size);
441
442   n = 0;
443   while (n < size) {
444     r = read (fd, &data[n], size - n);
445     if (r == -1) {
446       perrorf (g, "read: %s", filename);
447       free (data);
448       close (fd);
449       return -1;
450     }
451     if (r == 0) {
452       error (g, _("read: %s: unexpected end of file"), filename);
453       free (data);
454       close (fd);
455       return -1;
456     }
457     n += r;
458   }
459
460   if (close (fd) == -1) {
461     perrorf (g, "close: %s", filename);
462     free (data);
463     return -1;
464   }
465
466   *data_r = data;
467   *size_r = size;
468
469   return 0;
470 }