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