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