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