Add basic support for netbsd detection.
[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_NETBSD:
169   case OS_TYPE_UNKNOWN:
170   default: ;
171   }
172
173   if (r == NOT_FOUND) {
174     /* Not found, but not an error.  So return the special zero-length
175      * buffer.  Use malloc(1) here to ensure that malloc won't return
176      * NULL.
177      */
178     r = safe_malloc (g, 1);
179     size = 0;
180   }
181
182   *size_r = size;
183   return r;
184 }
185
186 /* Check that the named file 'filename' is a PNG file and is reasonable.
187  * If it is, download and return it.
188  */
189 static char *
190 get_png (guestfs_h *g, struct inspect_fs *fs, const char *filename,
191          size_t *size_r, size_t max_size)
192 {
193   char *ret = NOT_FOUND;
194   char *type = NULL;
195   char *local = NULL;
196   int r, w, h;
197
198   r = guestfs_exists (g, filename);
199   if (r == -1) {
200     ret = NULL; /* a real error */
201     goto out;
202   }
203   if (r == 0) goto out;
204
205   /* Check the file type and geometry. */
206   type = guestfs_file (g, filename);
207   if (!type) goto out;
208
209   if (!STRPREFIX (type, "PNG image data, ")) goto out;
210   if (sscanf (&type[16], "%d x %d", &w, &h) != 2) goto out;
211   if (w < 16 || h < 16 || w > 1024 || h > 1024) goto out;
212
213   /* Define a maximum reasonable size based on the geometry.  This
214    * also limits the maximum we allocate below to around 4 MB.
215    */
216   if (max_size == 0)
217     max_size = 4 * w * h;
218
219   local = guestfs___download_to_tmp (g, fs, filename, "icon", max_size);
220   if (!local) goto out;
221
222   /* Successfully passed checks and downloaded.  Read it into memory. */
223   if (read_whole_file (g, local, &ret, size_r) == -1) {
224     ret = NULL;
225     goto out;
226   }
227
228  out:
229   free (local);
230   free (type);
231
232   return ret;
233 }
234
235 /* Return /etc/favicon.png (or \etc\favicon.png) if it exists and if
236  * it has a reasonable size and format.
237  */
238 static char *
239 icon_favicon (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
240 {
241   char *ret;
242   char *filename = safe_strdup (g, "/etc/favicon.png");
243
244   if (fs->type == OS_TYPE_WINDOWS) {
245     char *f = guestfs___case_sensitive_path_silently (g, filename);
246     if (f) {
247       free (filename);
248       filename = f;
249     }
250   }
251
252   ret = get_png (g, fs, filename, size_r, 0);
253   free (filename);
254   return ret;
255 }
256
257 /* Return FEDORA_ICON.  I checked that this exists on at least Fedora 6
258  * through 16.
259  */
260 #define FEDORA_ICON "/usr/share/icons/hicolor/96x96/apps/fedora-logo-icon.png"
261
262 static char *
263 icon_fedora (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
264 {
265   return get_png (g, fs, FEDORA_ICON, size_r, 0);
266 }
267
268 /* RHEL 3, 4:
269  * /usr/share/pixmaps/redhat/shadowman-transparent.png is a 517x515
270  * PNG with alpha channel, around 64K in size.
271  *
272  * RHEL 5, 6:
273  * As above, but the file has been optimized to about 16K.
274  *
275  * Conveniently the RHEL clones also have the same file with the
276  * same name, but containing their own logos.  Sense prevails!
277  */
278 #define SHADOWMAN_ICON "/usr/share/pixmaps/redhat/shadowman-transparent.png"
279
280 static char *
281 icon_rhel (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
282 {
283   size_t max_size = 0;
284
285   if (fs->distro == OS_DISTRO_RHEL) {
286     if (fs->major_version <= 4)
287       max_size = 66000;
288     else
289       max_size = 17000;
290   }
291
292   return get_png (g, fs, SHADOWMAN_ICON, size_r, max_size);
293 }
294
295 #define DEBIAN_ICON "/usr/share/pixmaps/debian-logo.png"
296
297 static char *
298 icon_debian (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
299 {
300   return get_png (g, fs, DEBIAN_ICON, size_r, 2048);
301 }
302
303 #define UBUNTU_ICON "/usr/share/icons/gnome/24x24/places/ubuntu-logo.png"
304
305 static char *
306 icon_ubuntu (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
307 {
308   return get_png (g, fs, UBUNTU_ICON, size_r, 2048);
309 }
310
311 #define MAGEIA_ICON "/usr/share/icons/mageia.png"
312
313 static char *
314 icon_mageia (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
315 {
316   return get_png (g, fs, MAGEIA_ICON, size_r, 2048);
317 }
318
319 #define OPENSUSE_ICON "/usr/share/icons/hicolor/24x24/apps/distributor.png"
320
321 static char *
322 icon_opensuse (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
323 {
324   return get_png (g, fs, OPENSUSE_ICON, size_r, 2048);
325 }
326
327 /* Windows, as usual, has to be much more complicated and stupid than
328  * anything else.
329  *
330  * We have to download %systemroot%\explorer.exe and use a special
331  * program called 'wrestool' to extract the icons from this file.  For
332  * each version of Windows, the icon we want is in a different place.
333  * The icon is in a stupid format (BMP), and in some cases multiple
334  * icons are in a single BMP file so we have to do some manipulation
335  * on the file.
336  *
337  * XXX I've only bothered with this nonsense for a few versions of
338  * Windows that I have handy.  Please send patches to support other
339  * versions.
340  */
341
342 static char *
343 icon_windows_xp (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
344                  size_t *size_r)
345 {
346   char *ret;
347   char *pngfile;
348   char *cmd;
349   int r;
350
351   pngfile = safe_asprintf (g, "%s/windows-xp-icon.png", g->tmpdir);
352
353   cmd = safe_asprintf (g,
354                        "wrestool -x --type=2 --name=143 %s | "
355                        "bmptopnm | pnmtopng > %s",
356           explorer, pngfile);
357   r = system (cmd);
358   if (r == -1 || WEXITSTATUS (r) != 0) {
359     debug (g, "external command failed: %s", cmd);
360     free (cmd);
361     free (pngfile);
362     return NOT_FOUND;
363   }
364
365   free (cmd);
366
367   if (read_whole_file (g, pngfile, &ret, size_r) == -1) {
368     free (pngfile);
369     return NULL;
370   }
371
372   free (pngfile);
373
374   return ret;
375 }
376
377 static char *
378 icon_windows_7 (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
379                 size_t *size_r)
380 {
381   char *ret;
382   char *pngfile;
383   char *cmd;
384   int r;
385
386   pngfile = safe_asprintf (g, "%s/windows-7-icon.png", g->tmpdir);
387
388   cmd = safe_asprintf (g,
389                        "wrestool -x --type=2 --name=6801 %s | "
390                        "bmptopnm | pamcut -bottom 54 | pnmtopng > %s",
391           explorer, pngfile);
392   r = system (cmd);
393   if (r == -1 || WEXITSTATUS (r) != 0) {
394     debug (g, "external command failed: %s", cmd);
395     free (cmd);
396     free (pngfile);
397     return NOT_FOUND;
398   }
399
400   free (cmd);
401
402   if (read_whole_file (g, pngfile, &ret, size_r) == -1) {
403     free (pngfile);
404     return NULL;
405   }
406
407   free (pngfile);
408
409   return ret;
410 }
411
412 static char *
413 icon_windows (guestfs_h *g, struct inspect_fs *fs, size_t *size_r)
414 {
415   char *(*fn) (guestfs_h *g, struct inspect_fs *fs, const char *explorer,
416                size_t *size_r);
417   char *filename1, *filename2, *filename3;
418   char *ret;
419
420   /* Windows XP. */
421   if (fs->major_version == 5 && fs->minor_version == 1)
422     fn = icon_windows_xp;
423
424   /* Windows 7 */
425   else if (fs->major_version == 6 && fs->minor_version == 1)
426     fn = icon_windows_7;
427
428   /* Not (yet) a supported version of Windows. */
429   else return NOT_FOUND;
430
431   if (fs->windows_systemroot == NULL)
432     return NOT_FOUND;
433
434   /* Download %systemroot%\explorer.exe */
435   filename1 = safe_asprintf (g, "%s/explorer.exe", fs->windows_systemroot);
436   filename2 = guestfs___case_sensitive_path_silently (g, filename1);
437   free (filename1);
438   if (filename2 == NULL)
439     return NOT_FOUND;
440
441   filename3 = guestfs___download_to_tmp (g, fs, filename2, "explorer",
442                                          MAX_WINDOWS_EXPLORER_SIZE);
443   free (filename2);
444   if (filename3 == NULL)
445     return NOT_FOUND;
446
447   ret = fn (g, fs, filename3, size_r);
448   free (filename3);
449   return ret;
450 }
451
452 /* Read the whole file into a memory buffer and return it.  The file
453  * should be a regular, local, trusted file.
454  */
455 static int
456 read_whole_file (guestfs_h *g, const char *filename,
457                  char **data_r, size_t *size_r)
458 {
459   int fd;
460   char *data;
461   off_t size;
462   off_t n;
463   ssize_t r;
464   struct stat statbuf;
465
466   fd = open (filename, O_RDONLY);
467   if (fd == -1) {
468     perrorf (g, "open: %s", filename);
469     return -1;
470   }
471
472   if (fstat (fd, &statbuf) == -1) {
473     perrorf (g, "stat: %s", filename);
474     close (fd);
475     return -1;
476   }
477
478   size = statbuf.st_size;
479   data = safe_malloc (g, size);
480
481   n = 0;
482   while (n < size) {
483     r = read (fd, &data[n], size - n);
484     if (r == -1) {
485       perrorf (g, "read: %s", filename);
486       free (data);
487       close (fd);
488       return -1;
489     }
490     if (r == 0) {
491       error (g, _("read: %s: unexpected end of file"), filename);
492       free (data);
493       close (fd);
494       return -1;
495     }
496     n += r;
497   }
498
499   if (close (fd) == -1) {
500     perrorf (g, "close: %s", filename);
501     free (data);
502     return -1;
503   }
504
505   *data_r = data;
506   *size_r = size;
507
508   return 0;
509 }