configure: Add --with-extra flag to allow setting the extra version string.
[libguestfs.git] / src / inspect_apps.c
1 /* libguestfs
2  * Copyright (C) 2010-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 #include <endian.h>
31
32 #ifdef HAVE_PCRE
33 #include <pcre.h>
34 #endif
35
36 #ifdef HAVE_HIVEX
37 #include <hivex.h>
38 #endif
39
40 #include "c-ctype.h"
41 #include "ignore-value.h"
42 #include "xstrtol.h"
43
44 #include "guestfs.h"
45 #include "guestfs-internal.h"
46 #include "guestfs-internal-actions.h"
47 #include "guestfs_protocol.h"
48
49 #if defined(HAVE_PCRE) && defined(HAVE_HIVEX)
50
51 #ifdef DB_DUMP
52 static struct guestfs_application_list *list_applications_rpm (guestfs_h *g, struct inspect_fs *fs);
53 #endif
54 static struct guestfs_application_list *list_applications_deb (guestfs_h *g, struct inspect_fs *fs);
55 static struct guestfs_application_list *list_applications_windows (guestfs_h *g, struct inspect_fs *fs);
56 static void add_application (guestfs_h *g, struct guestfs_application_list *, const char *name, const char *display_name, int32_t epoch, const char *version, const char *release, const char *install_path, const char *publisher, const char *url, const char *description);
57 static void sort_applications (struct guestfs_application_list *);
58
59 /* Unlike the simple inspect-get-* calls, this one assumes that the
60  * disks are mounted up, and reads files from the mounted disks.
61  */
62 struct guestfs_application_list *
63 guestfs__inspect_list_applications (guestfs_h *g, const char *root)
64 {
65   struct inspect_fs *fs = guestfs___search_for_root (g, root);
66   if (!fs)
67     return NULL;
68
69   struct guestfs_application_list *ret = NULL;
70
71   /* Presently we can only list applications for installed disks.  It
72    * is possible in future to get lists of packages from installers.
73    */
74   if (fs->format == OS_FORMAT_INSTALLED) {
75     switch (fs->type) {
76     case OS_TYPE_LINUX:
77       switch (fs->package_format) {
78       case OS_PACKAGE_FORMAT_RPM:
79 #ifdef DB_DUMP
80         ret = list_applications_rpm (g, fs);
81         if (ret == NULL)
82           return NULL;
83 #endif
84         break;
85
86       case OS_PACKAGE_FORMAT_DEB:
87         ret = list_applications_deb (g, fs);
88         if (ret == NULL)
89           return NULL;
90         break;
91
92       case OS_PACKAGE_FORMAT_PACMAN:
93       case OS_PACKAGE_FORMAT_EBUILD:
94       case OS_PACKAGE_FORMAT_PISI:
95       case OS_PACKAGE_FORMAT_UNKNOWN:
96       default:
97         /* nothing - keep GCC happy */;
98       }
99       break;
100
101     case OS_TYPE_WINDOWS:
102       ret = list_applications_windows (g, fs);
103       if (ret == NULL)
104         return NULL;
105       break;
106
107     case OS_TYPE_FREEBSD:
108     case OS_TYPE_UNKNOWN:
109     default:
110       /* nothing - keep GCC happy */;
111     }
112   }
113
114   if (ret == NULL) {
115     /* Don't know how to do inspection.  Not an error, return an
116      * empty list.
117      */
118     ret = safe_malloc (g, sizeof *ret);
119     ret->len = 0;
120     ret->val = NULL;
121   }
122
123   sort_applications (ret);
124
125   return ret;
126 }
127
128 #ifdef DB_DUMP
129
130 static int
131 read_rpm_name (guestfs_h *g,
132                const unsigned char *key, size_t keylen,
133                const unsigned char *value, size_t valuelen,
134                void *appsv)
135 {
136   struct guestfs_application_list *apps = appsv;
137   char *name;
138
139   /* The name (key) field won't be NUL-terminated, so we must do that. */
140   name = safe_malloc (g, keylen+1);
141   memcpy (name, key, keylen);
142   name[keylen] = '\0';
143
144   add_application (g, apps, name, "", 0, "", "", "", "", "", "");
145
146   free (name);
147
148   return 0;
149 }
150
151 static struct guestfs_application_list *
152 list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
153 {
154   char *Name = NULL;
155
156   Name = guestfs___download_to_tmp (g, fs,
157                                     "/var/lib/rpm/Name", "rpm_Name",
158                                     MAX_PKG_DB_SIZE);
159   if (Name == NULL)
160     return NULL;
161
162   /* Allocate 'apps' list. */
163   struct guestfs_application_list *apps;
164   apps = safe_malloc (g, sizeof *apps);
165   apps->len = 0;
166   apps->val = NULL;
167
168   if (guestfs___read_db_dump (g, Name, apps, read_rpm_name) == -1) {
169     guestfs_free_application_list (apps);
170     free (Name);
171     return NULL;
172   }
173
174   return apps;
175 }
176
177 #endif /* defined DB_DUMP */
178
179 static struct guestfs_application_list *
180 list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
181 {
182   char *status = NULL;
183   status = guestfs___download_to_tmp (g, fs, "/var/lib/dpkg/status", "status",
184                                       MAX_PKG_DB_SIZE);
185   if (status == NULL)
186     return NULL;
187
188   struct guestfs_application_list *apps = NULL, *ret = NULL;
189   FILE *fp = NULL;
190   char line[1024];
191   size_t len;
192   char *name = NULL, *version = NULL, *release = NULL;
193   int installed_flag = 0;
194
195   fp = fopen (status, "r");
196   if (fp == NULL) {
197     perrorf (g, "fopen: %s", status);
198     goto out;
199   }
200
201   /* Allocate 'apps' list. */
202   apps = safe_malloc (g, sizeof *apps);
203   apps->len = 0;
204   apps->val = NULL;
205
206   /* Read the temporary file.  Each package entry is separated by
207    * a blank line.
208    * XXX Strictly speaking this is in mailbox header format, so it
209    * would be possible for fields to spread across multiple lines,
210    * although for the short fields that we are concerned about this is
211    * unlikely and not seen in practice.
212    */
213   while (fgets (line, sizeof line, fp) != NULL) {
214     len = strlen (line);
215     if (len > 0 && line[len-1] == '\n') {
216       line[len-1] = '\0';
217       len--;
218     }
219
220     if (STRPREFIX (line, "Package: ")) {
221       free (name);
222       name = safe_strdup (g, &line[9]);
223     }
224     else if (STRPREFIX (line, "Status: ")) {
225       installed_flag = strstr (&line[8], "installed") != NULL;
226     }
227     else if (STRPREFIX (line, "Version: ")) {
228       free (version);
229       free (release);
230       char *p = strchr (&line[9], '-');
231       if (p) {
232         *p = '\0';
233         version = safe_strdup (g, &line[9]);
234         release = safe_strdup (g, p+1);
235       } else {
236         version = safe_strdup (g, &line[9]);
237         release = NULL;
238       }
239     }
240     else if (STREQ (line, "")) {
241       if (installed_flag && name && version)
242         add_application (g, apps, name, "", 0, version, release ? : "",
243                          "", "", "", "");
244       free (name);
245       free (version);
246       free (release);
247       name = version = release = NULL;
248       installed_flag = 0;
249     }
250   }
251
252   if (fclose (fp) == -1) {
253     perrorf (g, "fclose: %s", status);
254     goto out;
255   }
256   fp = NULL;
257
258   ret = apps;
259
260  out:
261   if (ret == NULL && apps != NULL)
262     guestfs_free_application_list (apps);
263   if (fp)
264     fclose (fp);
265   free (name);
266   free (version);
267   free (release);
268   free (status);
269   return ret;
270 }
271
272 static void list_applications_windows_from_path (guestfs_h *g, hive_h *h, struct guestfs_application_list *apps, const char **path, size_t path_len);
273
274 static struct guestfs_application_list *
275 list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
276 {
277   size_t len = strlen (fs->windows_systemroot) + 64;
278   char software[len];
279   snprintf (software, len, "%s/system32/config/software",
280             fs->windows_systemroot);
281
282   char *software_path = guestfs___case_sensitive_path_silently (g, software);
283   if (!software_path) {
284     /* Missing software hive is a problem. */
285     error (g, "no HKLM\\SOFTWARE hive found in the guest");
286     return NULL;
287   }
288
289   char *software_hive = NULL;
290   struct guestfs_application_list *ret = NULL;
291   hive_h *h = NULL;
292
293   software_hive = guestfs___download_to_tmp (g, fs, software_path, "software",
294                                              MAX_REGISTRY_SIZE);
295   if (software_hive == NULL)
296     goto out;
297
298   free (software_path);
299   software_path = NULL;
300
301   h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
302   if (h == NULL) {
303     perrorf (g, "hivex_open");
304     goto out;
305   }
306
307   /* Allocate apps list. */
308   ret = safe_malloc (g, sizeof *ret);
309   ret->len = 0;
310   ret->val = NULL;
311
312   /* Ordinary native applications. */
313   const char *hivepath[] =
314     { "Microsoft", "Windows", "CurrentVersion", "Uninstall" };
315   list_applications_windows_from_path (g, h, ret, hivepath,
316                                        sizeof hivepath / sizeof hivepath[0]);
317
318   /* 32-bit emulated Windows apps running on the WOW64 emulator.
319    * http://support.microsoft.com/kb/896459 (RHBZ#692545).
320    */
321   const char *hivepath2[] =
322     { "WOW6432node", "Microsoft", "Windows", "CurrentVersion", "Uninstall" };
323   list_applications_windows_from_path (g, h, ret, hivepath2,
324                                        sizeof hivepath2 / sizeof hivepath2[0]);
325
326  out:
327   if (h) hivex_close (h);
328   free (software_path);
329   free (software_hive);
330
331   return ret;
332 }
333
334 static void
335 list_applications_windows_from_path (guestfs_h *g, hive_h *h,
336                                      struct guestfs_application_list *apps,
337                                      const char **path, size_t path_len)
338 {
339   hive_node_h *children = NULL;
340   hive_node_h node;
341   size_t i;
342
343   node = hivex_root (h);
344
345   for (i = 0; node != 0 && i < path_len; ++i)
346     node = hivex_node_get_child (h, node, path[i]);
347
348   if (node == 0)
349     return;
350
351   children = hivex_node_children (h, node);
352   if (children == NULL)
353     return;
354
355   /* Consider any child node that has a DisplayName key.
356    * See also:
357    * http://nsis.sourceforge.net/Add_uninstall_information_to_Add/Remove_Programs#Optional_values
358    */
359   for (i = 0; children[i] != 0; ++i) {
360     hive_value_h value;
361     char *name = NULL;
362     char *display_name = NULL;
363     char *version = NULL;
364     char *install_path = NULL;
365     char *publisher = NULL;
366     char *url = NULL;
367     char *comments = NULL;
368
369     /* Use the node name as a proxy for the package name in Linux.  The
370      * display name is not language-independent, so it cannot be used.
371      */
372     name = hivex_node_name (h, children[i]);
373     if (name == NULL)
374       continue;
375
376     value = hivex_node_get_value (h, children[i], "DisplayName");
377     if (value) {
378       display_name = hivex_value_string (h, value);
379       if (display_name) {
380         value = hivex_node_get_value (h, children[i], "DisplayVersion");
381         if (value)
382           version = hivex_value_string (h, value);
383         value = hivex_node_get_value (h, children[i], "InstallLocation");
384         if (value)
385           install_path = hivex_value_string (h, value);
386         value = hivex_node_get_value (h, children[i], "Publisher");
387         if (value)
388           publisher = hivex_value_string (h, value);
389         value = hivex_node_get_value (h, children[i], "URLInfoAbout");
390         if (value)
391           url = hivex_value_string (h, value);
392         value = hivex_node_get_value (h, children[i], "Comments");
393         if (value)
394           comments = hivex_value_string (h, value);
395
396         add_application (g, apps, name, display_name, 0,
397                          version ? : "",
398                          "",
399                          install_path ? : "",
400                          publisher ? : "",
401                          url ? : "",
402                          comments ? : "");
403       }
404     }
405
406     free (name);
407     free (display_name);
408     free (version);
409     free (install_path);
410     free (publisher);
411     free (url);
412     free (comments);
413   }
414
415   free (children);
416 }
417
418 static void
419 add_application (guestfs_h *g, struct guestfs_application_list *apps,
420                  const char *name, const char *display_name, int32_t epoch,
421                  const char *version, const char *release,
422                  const char *install_path,
423                  const char *publisher, const char *url,
424                  const char *description)
425 {
426   apps->len++;
427   apps->val = safe_realloc (g, apps->val,
428                             apps->len * sizeof (struct guestfs_application));
429   apps->val[apps->len-1].app_name = safe_strdup (g, name);
430   apps->val[apps->len-1].app_display_name = safe_strdup (g, display_name);
431   apps->val[apps->len-1].app_epoch = epoch;
432   apps->val[apps->len-1].app_version = safe_strdup (g, version);
433   apps->val[apps->len-1].app_release = safe_strdup (g, release);
434   apps->val[apps->len-1].app_install_path = safe_strdup (g, install_path);
435   /* XXX Translated path is not implemented yet. */
436   apps->val[apps->len-1].app_trans_path = safe_strdup (g, "");
437   apps->val[apps->len-1].app_publisher = safe_strdup (g, publisher);
438   apps->val[apps->len-1].app_url = safe_strdup (g, url);
439   /* XXX The next two are not yet implemented for any package
440    * format, but we could easily support them for rpm and deb.
441    */
442   apps->val[apps->len-1].app_source_package = safe_strdup (g, "");
443   apps->val[apps->len-1].app_summary = safe_strdup (g, "");
444   apps->val[apps->len-1].app_description = safe_strdup (g, description);
445 }
446
447 /* Sort applications by name before returning the list. */
448 static int
449 compare_applications (const void *vp1, const void *vp2)
450 {
451   const struct guestfs_application *v1 = vp1;
452   const struct guestfs_application *v2 = vp2;
453
454   return strcmp (v1->app_name, v2->app_name);
455 }
456
457 static void
458 sort_applications (struct guestfs_application_list *apps)
459 {
460   if (apps && apps->val)
461     qsort (apps->val, apps->len, sizeof (struct guestfs_application),
462            compare_applications);
463 }
464
465 #else /* no PCRE or hivex at compile time */
466
467 /* XXX These functions should be in an optgroup. */
468
469 #define NOT_IMPL(r)                                                     \
470   error (g, _("inspection API not available since this version of libguestfs was compiled without PCRE or hivex libraries")); \
471   return r
472
473 struct guestfs_application_list *
474 guestfs__inspect_list_applications (guestfs_h *g, const char *root)
475 {
476   NOT_IMPL(NULL);
477 }
478
479 #endif /* no PCRE or hivex at compile time */