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