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