inspect: Split code into separate files.
[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 static struct guestfs_application_list *
130 list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
131 {
132   const char *basename = "rpm_Name";
133   char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
134   snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
135             g->tmpdir, basename);
136
137   if (guestfs___download_to_tmp (g, "/var/lib/rpm/Name", basename,
138                                  MAX_PKG_DB_SIZE) == -1)
139     return NULL;
140
141   struct guestfs_application_list *apps = NULL, *ret = NULL;
142 #define cmd_len (strlen (tmpdir_basename) + 64)
143   char cmd[cmd_len];
144   FILE *pp = NULL;
145   char line[1024];
146   size_t len;
147
148   snprintf (cmd, cmd_len, DB_DUMP " -p '%s'", tmpdir_basename);
149
150   debug (g, "list_applications_rpm: %s", cmd);
151
152   pp = popen (cmd, "r");
153   if (pp == NULL) {
154     perrorf (g, "popen: %s", cmd);
155     goto out;
156   }
157
158   /* Ignore everything to end-of-header marker. */
159   for (;;) {
160     if (fgets (line, sizeof line, pp) == NULL) {
161       error (g, _("unexpected end of output from db_dump command"));
162       goto out;
163     }
164
165     len = strlen (line);
166     if (len > 0 && line[len-1] == '\n') {
167       line[len-1] = '\0';
168       len--;
169     }
170
171     if (STREQ (line, "HEADER=END"))
172       break;
173   }
174
175   /* Allocate 'apps' list. */
176   apps = safe_malloc (g, sizeof *apps);
177   apps->len = 0;
178   apps->val = NULL;
179
180   /* Read alternate lines until end of data marker. */
181   for (;;) {
182     if (fgets (line, sizeof line, pp) == NULL) {
183       error (g, _("unexpected end of output from db_dump command"));
184       goto out;
185     }
186
187     len = strlen (line);
188     if (len > 0 && line[len-1] == '\n') {
189       line[len-1] = '\0';
190       len--;
191     }
192
193     if (STREQ (line, "DATA=END"))
194       break;
195
196     char *p = line;
197     if (len > 0 && line[0] == ' ')
198       p = line+1;
199     /* Ignore any application name that contains non-printable chars.
200      * In the db_dump output these would be escaped with backslash, so
201      * we can just ignore any such line.
202      */
203     if (strchr (p, '\\') == NULL)
204       add_application (g, apps, p, "", 0, "", "", "", "", "", "");
205
206     /* Discard next line. */
207     if (fgets (line, sizeof line, pp) == NULL) {
208       error (g, _("unexpected end of output from db_dump command"));
209       goto out;
210     }
211   }
212
213   /* Catch errors from the db_dump command. */
214   if (pclose (pp) == -1) {
215     perrorf (g, "pclose: %s", cmd);
216     goto out;
217   }
218   pp = NULL;
219
220   ret = apps;
221
222  out:
223   if (ret == NULL && apps != NULL)
224     guestfs_free_application_list (apps);
225   if (pp)
226     pclose (pp);
227
228   return ret;
229 }
230 #endif /* defined DB_DUMP */
231
232 static struct guestfs_application_list *
233 list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
234 {
235   const char *basename = "deb_status";
236   char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
237   snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
238             g->tmpdir, basename);
239
240   if (guestfs___download_to_tmp (g, "/var/lib/dpkg/status", basename,
241                                  MAX_PKG_DB_SIZE) == -1)
242     return NULL;
243
244   struct guestfs_application_list *apps = NULL, *ret = NULL;
245   FILE *fp = NULL;
246   char line[1024];
247   size_t len;
248   char *name = NULL, *version = NULL, *release = NULL;
249   int installed_flag = 0;
250
251   fp = fopen (tmpdir_basename, "r");
252   if (fp == NULL) {
253     perrorf (g, "fopen: %s", tmpdir_basename);
254     goto out;
255   }
256
257   /* Allocate 'apps' list. */
258   apps = safe_malloc (g, sizeof *apps);
259   apps->len = 0;
260   apps->val = NULL;
261
262   /* Read the temporary file.  Each package entry is separated by
263    * a blank line.
264    * XXX Strictly speaking this is in mailbox header format, so it
265    * would be possible for fields to spread across multiple lines,
266    * although for the short fields that we are concerned about this is
267    * unlikely and not seen in practice.
268    */
269   while (fgets (line, sizeof line, fp) != NULL) {
270     len = strlen (line);
271     if (len > 0 && line[len-1] == '\n') {
272       line[len-1] = '\0';
273       len--;
274     }
275
276     if (STRPREFIX (line, "Package: ")) {
277       free (name);
278       name = safe_strdup (g, &line[9]);
279     }
280     else if (STRPREFIX (line, "Status: ")) {
281       installed_flag = strstr (&line[8], "installed") != NULL;
282     }
283     else if (STRPREFIX (line, "Version: ")) {
284       free (version);
285       free (release);
286       char *p = strchr (&line[9], '-');
287       if (p) {
288         *p = '\0';
289         version = safe_strdup (g, &line[9]);
290         release = safe_strdup (g, p+1);
291       } else {
292         version = safe_strdup (g, &line[9]);
293         release = NULL;
294       }
295     }
296     else if (STREQ (line, "")) {
297       if (installed_flag && name && version)
298         add_application (g, apps, name, "", 0, version, release ? : "",
299                          "", "", "", "");
300       free (name);
301       free (version);
302       free (release);
303       name = version = release = NULL;
304       installed_flag = 0;
305     }
306   }
307
308   if (fclose (fp) == -1) {
309     perrorf (g, "fclose: %s", tmpdir_basename);
310     goto out;
311   }
312   fp = NULL;
313
314   ret = apps;
315
316  out:
317   if (ret == NULL && apps != NULL)
318     guestfs_free_application_list (apps);
319   if (fp)
320     fclose (fp);
321   free (name);
322   free (version);
323   free (release);
324   return ret;
325 }
326
327 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);
328
329 static struct guestfs_application_list *
330 list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
331 {
332   const char *basename = "software";
333   char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
334   snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
335             g->tmpdir, basename);
336
337   size_t len = strlen (fs->windows_systemroot) + 64;
338   char software[len];
339   snprintf (software, len, "%s/system32/config/software",
340             fs->windows_systemroot);
341
342   char *software_path = guestfs___case_sensitive_path_silently (g, software);
343   if (!software_path)
344     /* If the software hive doesn't exist, just accept that we cannot
345      * list windows apps.
346      */
347     return 0;
348
349   struct guestfs_application_list *ret = NULL;
350   hive_h *h = NULL;
351
352   if (guestfs___download_to_tmp (g, software_path, basename,
353                                  MAX_REGISTRY_SIZE) == -1)
354     goto out;
355
356   free (software_path);
357   software_path = NULL;
358
359   h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
360   if (h == NULL) {
361     perrorf (g, "hivex_open");
362     goto out;
363   }
364
365   /* Allocate apps list. */
366   ret = safe_malloc (g, sizeof *ret);
367   ret->len = 0;
368   ret->val = NULL;
369
370   /* Ordinary native applications. */
371   const char *hivepath[] =
372     { "Microsoft", "Windows", "CurrentVersion", "Uninstall" };
373   list_applications_windows_from_path (g, h, ret, hivepath,
374                                        sizeof hivepath / sizeof hivepath[0]);
375
376   /* 32-bit emulated Windows apps running on the WOW64 emulator.
377    * http://support.microsoft.com/kb/896459 (RHBZ#692545).
378    */
379   const char *hivepath2[] =
380     { "WOW6432node", "Microsoft", "Windows", "CurrentVersion", "Uninstall" };
381   list_applications_windows_from_path (g, h, ret, hivepath2,
382                                        sizeof hivepath2 / sizeof hivepath2[0]);
383
384  out:
385   if (h) hivex_close (h);
386   free (software_path);
387
388   return ret;
389 }
390
391 static void
392 list_applications_windows_from_path (guestfs_h *g, hive_h *h,
393                                      struct guestfs_application_list *apps,
394                                      const char **path, size_t path_len)
395 {
396   hive_node_h *children = NULL;
397   hive_node_h node;
398   size_t i;
399
400   node = hivex_root (h);
401
402   for (i = 0; node != 0 && i < path_len; ++i)
403     node = hivex_node_get_child (h, node, path[i]);
404
405   if (node == 0)
406     return;
407
408   children = hivex_node_children (h, node);
409   if (children == NULL)
410     return;
411
412   /* Consider any child node that has a DisplayName key.
413    * See also:
414    * http://nsis.sourceforge.net/Add_uninstall_information_to_Add/Remove_Programs#Optional_values
415    */
416   for (i = 0; children[i] != 0; ++i) {
417     hive_value_h value;
418     char *name = NULL;
419     char *display_name = NULL;
420     char *version = NULL;
421     char *install_path = NULL;
422     char *publisher = NULL;
423     char *url = NULL;
424     char *comments = NULL;
425
426     /* Use the node name as a proxy for the package name in Linux.  The
427      * display name is not language-independent, so it cannot be used.
428      */
429     name = hivex_node_name (h, children[i]);
430     if (name == NULL)
431       continue;
432
433     value = hivex_node_get_value (h, children[i], "DisplayName");
434     if (value) {
435       display_name = hivex_value_string (h, value);
436       if (display_name) {
437         value = hivex_node_get_value (h, children[i], "DisplayVersion");
438         if (value)
439           version = hivex_value_string (h, value);
440         value = hivex_node_get_value (h, children[i], "InstallLocation");
441         if (value)
442           install_path = hivex_value_string (h, value);
443         value = hivex_node_get_value (h, children[i], "Publisher");
444         if (value)
445           publisher = hivex_value_string (h, value);
446         value = hivex_node_get_value (h, children[i], "URLInfoAbout");
447         if (value)
448           url = hivex_value_string (h, value);
449         value = hivex_node_get_value (h, children[i], "Comments");
450         if (value)
451           comments = hivex_value_string (h, value);
452
453         add_application (g, apps, name, display_name, 0,
454                          version ? : "",
455                          "",
456                          install_path ? : "",
457                          publisher ? : "",
458                          url ? : "",
459                          comments ? : "");
460       }
461     }
462
463     free (name);
464     free (display_name);
465     free (version);
466     free (install_path);
467     free (publisher);
468     free (url);
469     free (comments);
470   }
471
472   free (children);
473 }
474
475 static void
476 add_application (guestfs_h *g, struct guestfs_application_list *apps,
477                  const char *name, const char *display_name, int32_t epoch,
478                  const char *version, const char *release,
479                  const char *install_path,
480                  const char *publisher, const char *url,
481                  const char *description)
482 {
483   apps->len++;
484   apps->val = safe_realloc (g, apps->val,
485                             apps->len * sizeof (struct guestfs_application));
486   apps->val[apps->len-1].app_name = safe_strdup (g, name);
487   apps->val[apps->len-1].app_display_name = safe_strdup (g, display_name);
488   apps->val[apps->len-1].app_epoch = epoch;
489   apps->val[apps->len-1].app_version = safe_strdup (g, version);
490   apps->val[apps->len-1].app_release = safe_strdup (g, release);
491   apps->val[apps->len-1].app_install_path = safe_strdup (g, install_path);
492   /* XXX Translated path is not implemented yet. */
493   apps->val[apps->len-1].app_trans_path = safe_strdup (g, "");
494   apps->val[apps->len-1].app_publisher = safe_strdup (g, publisher);
495   apps->val[apps->len-1].app_url = safe_strdup (g, url);
496   /* XXX The next two are not yet implemented for any package
497    * format, but we could easily support them for rpm and deb.
498    */
499   apps->val[apps->len-1].app_source_package = safe_strdup (g, "");
500   apps->val[apps->len-1].app_summary = safe_strdup (g, "");
501   apps->val[apps->len-1].app_description = safe_strdup (g, description);
502 }
503
504 /* Sort applications by name before returning the list. */
505 static int
506 compare_applications (const void *vp1, const void *vp2)
507 {
508   const struct guestfs_application *v1 = vp1;
509   const struct guestfs_application *v2 = vp2;
510
511   return strcmp (v1->app_name, v2->app_name);
512 }
513
514 static void
515 sort_applications (struct guestfs_application_list *apps)
516 {
517   if (apps && apps->val)
518     qsort (apps->val, apps->len, sizeof (struct guestfs_application),
519            compare_applications);
520 }
521
522 #else /* no PCRE or hivex at compile time */
523
524 /* XXX These functions should be in an optgroup. */
525
526 #define NOT_IMPL(r)                                                     \
527   error (g, _("inspection API not available since this version of libguestfs was compiled without PCRE or hivex libraries")); \
528   return r
529
530 struct guestfs_application_list *
531 guestfs__inspect_list_applications (guestfs_h *g, const char *root)
532 {
533   NOT_IMPL(NULL);
534 }
535
536 #endif /* no PCRE or hivex at compile time */