daemon: debug segv correct use of dereferencing NULL.
[libguestfs.git] / src / inspect_fs_windows.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 #include <pcre.h>
33
34 #ifdef HAVE_HIVEX
35 #include <hivex.h>
36 #endif
37
38 #include "c-ctype.h"
39 #include "ignore-value.h"
40 #include "xstrtol.h"
41
42 #include "guestfs.h"
43 #include "guestfs-internal.h"
44 #include "guestfs-internal-actions.h"
45 #include "guestfs_protocol.h"
46
47 #if defined(HAVE_HIVEX)
48
49 /* Compile all the regular expressions once when the shared library is
50  * loaded.  PCRE is thread safe so we're supposedly OK here if
51  * multiple threads call into the libguestfs API functions below
52  * simultaneously.
53  */
54 static pcre *re_windows_version;
55
56 static void compile_regexps (void) __attribute__((constructor));
57 static void free_regexps (void) __attribute__((destructor));
58
59 static void
60 compile_regexps (void)
61 {
62   const char *err;
63   int offset;
64
65 #define COMPILE(re,pattern,options)                                     \
66   do {                                                                  \
67     re = pcre_compile ((pattern), (options), &err, &offset, NULL);      \
68     if (re == NULL) {                                                   \
69       ignore_value (write (2, err, strlen (err)));                      \
70       abort ();                                                         \
71     }                                                                   \
72   } while (0)
73
74   COMPILE (re_windows_version, "^(\\d+)\\.(\\d+)", 0);
75 }
76
77 static void
78 free_regexps (void)
79 {
80   pcre_free (re_windows_version);
81 }
82
83 static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs);
84 static int check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs);
85 static int check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs);
86 static char *map_registry_disk_blob (guestfs_h *g, const char *blob);
87
88 /* XXX Handling of boot.ini in the Perl version was pretty broken.  It
89  * essentially didn't do anything for modern Windows guests.
90  * Therefore I've omitted all that code.
91  */
92
93 /* Try to find Windows systemroot using some common locations.
94  *
95  * Notes:
96  *
97  * (1) We check for some directories inside to see if it is a real
98  * systemroot, and not just a directory that happens to have the same
99  * name.
100  *
101  * (2) If a Windows guest has multiple disks and applications are
102  * installed on those other disks, then those other disks will contain
103  * "/Program Files" and "/System Volume Information".  Those would
104  * *not* be Windows root disks.  (RHBZ#674130)
105  */
106 static const char *systemroots[] =
107   { "/windows", "/winnt", "/win32", "/win", NULL };
108
109 int
110 guestfs___has_windows_systemroot (guestfs_h *g)
111 {
112   size_t i;
113   char *systemroot, *p;
114   char path[256];
115
116   for (i = 0; i < sizeof systemroots / sizeof systemroots[0]; ++i) {
117     systemroot = guestfs___case_sensitive_path_silently (g, systemroots[i]);
118     if (!systemroot)
119       continue;
120
121     snprintf (path, sizeof path, "%s/system32", systemroot);
122     if (!guestfs___is_dir_nocase (g, path)) {
123       free (systemroot);
124       continue;
125     }
126
127     snprintf (path, sizeof path, "%s/system32/config", systemroot);
128     if (!guestfs___is_dir_nocase (g, path)) {
129       free (systemroot);
130       continue;
131     }
132
133     snprintf (path, sizeof path, "%s/system32/cmd.exe", systemroot);
134     if (!guestfs___is_file_nocase (g, path)) {
135       free (systemroot);
136       continue;
137     }
138
139     free (systemroot);
140
141     return (int)i;
142   }
143
144   return -1; /* not found */
145 }
146
147 int
148 guestfs___check_windows_root (guestfs_h *g, struct inspect_fs *fs)
149 {
150   int i;
151   char *systemroot;
152
153   fs->type = OS_TYPE_WINDOWS;
154   fs->distro = OS_DISTRO_WINDOWS;
155
156   i = guestfs___has_windows_systemroot (g);
157   if (i == -1) {
158     error (g, "check_windows_root: has_windows_systemroot unexpectedly returned -1");
159     return -1;
160   }
161
162   systemroot = guestfs___case_sensitive_path_silently (g, systemroots[i]);
163   if (!systemroot) {
164     error (g, _("cannot resolve Windows %%SYSTEMROOT%%"));
165     return -1;
166   }
167
168   debug (g, "windows %%SYSTEMROOT%% = %s", systemroot);
169
170   /* Freed by guestfs___free_inspect_info. */
171   fs->windows_systemroot = systemroot;
172
173   if (check_windows_arch (g, fs) == -1)
174     return -1;
175
176   /* Product name and version. */
177   if (check_windows_software_registry (g, fs) == -1)
178     return -1;
179
180   /* Hostname. */
181   if (check_windows_system_registry (g, fs) == -1)
182     return -1;
183
184   return 0;
185 }
186
187 static int
188 check_windows_arch (guestfs_h *g, struct inspect_fs *fs)
189 {
190   size_t len = strlen (fs->windows_systemroot) + 32;
191   char cmd_exe[len];
192   snprintf (cmd_exe, len, "%s/system32/cmd.exe", fs->windows_systemroot);
193
194   char *cmd_exe_path = guestfs___case_sensitive_path_silently (g, cmd_exe);
195   if (!cmd_exe_path)
196     return 0;
197
198   char *arch = guestfs_file_architecture (g, cmd_exe_path);
199   free (cmd_exe_path);
200
201   if (arch)
202     fs->arch = arch;        /* freed by guestfs___free_inspect_info */
203
204   return 0;
205 }
206
207 /* At the moment, pull just the ProductName and version numbers from
208  * the registry.  In future there is a case for making many more
209  * registry fields available to callers.
210  */
211 static int
212 check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs)
213 {
214   size_t len = strlen (fs->windows_systemroot) + 64;
215   char software[len];
216   snprintf (software, len, "%s/system32/config/software",
217             fs->windows_systemroot);
218
219   char *software_path = guestfs___case_sensitive_path_silently (g, software);
220   if (!software_path)
221     /* If the software hive doesn't exist, just accept that we cannot
222      * find product_name etc.
223      */
224     return 0;
225
226   char *software_hive = NULL;
227   int ret = -1;
228   hive_h *h = NULL;
229   hive_value_h *values = NULL;
230
231   software_hive = guestfs___download_to_tmp (g, fs, software_path, "software",
232                                              MAX_REGISTRY_SIZE);
233   if (software_hive == NULL)
234     goto out;
235
236   h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
237   if (h == NULL) {
238     perrorf (g, "hivex_open");
239     goto out;
240   }
241
242   hive_node_h node = hivex_root (h);
243   const char *hivepath[] =
244     { "Microsoft", "Windows NT", "CurrentVersion" };
245   size_t i;
246   for (i = 0;
247        node != 0 && i < sizeof hivepath / sizeof hivepath[0];
248        ++i) {
249     node = hivex_node_get_child (h, node, hivepath[i]);
250   }
251
252   if (node == 0) {
253     perrorf (g, "hivex: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
254     goto out;
255   }
256
257   values = hivex_node_values (h, node);
258
259   for (i = 0; values[i] != 0; ++i) {
260     char *key = hivex_value_key (h, values[i]);
261     if (key == NULL) {
262       perrorf (g, "hivex_value_key");
263       goto out;
264     }
265
266     if (STRCASEEQ (key, "ProductName")) {
267       fs->product_name = hivex_value_string (h, values[i]);
268       if (!fs->product_name) {
269         perrorf (g, "hivex_value_string");
270         free (key);
271         goto out;
272       }
273     }
274     else if (STRCASEEQ (key, "CurrentVersion")) {
275       char *version = hivex_value_string (h, values[i]);
276       if (!version) {
277         perrorf (g, "hivex_value_string");
278         free (key);
279         goto out;
280       }
281       char *major, *minor;
282       if (match2 (g, version, re_windows_version, &major, &minor)) {
283         fs->major_version = guestfs___parse_unsigned_int (g, major);
284         free (major);
285         if (fs->major_version == -1) {
286           free (minor);
287           free (key);
288           free (version);
289           goto out;
290         }
291         fs->minor_version = guestfs___parse_unsigned_int (g, minor);
292         free (minor);
293         if (fs->minor_version == -1) {
294           free (key);
295           free (version);
296           goto out;
297         }
298       }
299
300       free (version);
301     }
302     else if (STRCASEEQ (key, "InstallationType")) {
303       fs->product_variant = hivex_value_string (h, values[i]);
304       if (!fs->product_variant) {
305         perrorf (g, "hivex_value_string");
306         free (key);
307         goto out;
308       }
309     }
310
311     free (key);
312   }
313
314   ret = 0;
315
316  out:
317   if (h) hivex_close (h);
318   free (values);
319   free (software_path);
320   free (software_hive);
321
322   return ret;
323 }
324
325 static int
326 check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs)
327 {
328   size_t len = strlen (fs->windows_systemroot) + 64;
329   char system[len];
330   snprintf (system, len, "%s/system32/config/system",
331             fs->windows_systemroot);
332
333   char *system_path = guestfs___case_sensitive_path_silently (g, system);
334   if (!system_path)
335     /* If the system hive doesn't exist, just accept that we cannot
336      * find hostname etc.
337      */
338     return 0;
339
340   char *system_hive = NULL;
341   int ret = -1;
342   hive_h *h = NULL;
343   hive_node_h root, node;
344   hive_value_h value, *values = NULL;
345   int32_t dword;
346   size_t i, count;
347
348   system_hive =
349     guestfs___download_to_tmp (g, fs, system_path, "system",
350                                MAX_REGISTRY_SIZE);
351   if (system_hive == NULL)
352     goto out;
353
354   h = hivex_open (system_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
355   if (h == NULL) {
356     perrorf (g, "hivex_open");
357     goto out;
358   }
359
360   root = hivex_root (h);
361   if (root == 0) {
362     perrorf (g, "hivex_root");
363     goto out;
364   }
365
366   /* Get the CurrentControlSet. */
367   errno = 0;
368   node = hivex_node_get_child (h, root, "Select");
369   if (node == 0) {
370     if (errno != 0)
371       perrorf (g, "hivex_node_get_child");
372     else
373       error (g, "hivex: could not locate HKLM\\SYSTEM\\Select");
374     goto out;
375   }
376
377   errno = 0;
378   value = hivex_node_get_value (h, node, "Current");
379   if (value == 0) {
380     if (errno != 0)
381       perrorf (g, "hivex_node_get_value");
382     else
383       error (g, "hivex: HKLM\\System\\Select Default entry not found.");
384     goto out;
385   }
386
387   /* XXX Should check the type. */
388   dword = hivex_value_dword (h, value);
389   fs->windows_current_control_set = safe_asprintf (g, "ControlSet%03d", dword);
390
391   /* Get the drive mappings.
392    * This page explains the contents of HKLM\System\MountedDevices:
393    * http://www.goodells.net/multiboot/partsigs.shtml
394    */
395   errno = 0;
396   node = hivex_node_get_child (h, root, "MountedDevices");
397   if (node == 0) {
398     if (errno != 0)
399       perrorf (g, "hivex_node_get_child");
400     else
401       error (g, "hivex: could not locate HKLM\\SYSTEM\\MountedDevices");
402     goto out;
403   }
404
405   values = hivex_node_values (h, node);
406
407   /* Count how many DOS drive letter mappings there are.  This doesn't
408    * ignore removable devices, so it overestimates, but that doesn't
409    * matter because it just means we'll allocate a few bytes extra.
410    */
411   for (i = count = 0; values[i] != 0; ++i) {
412     char *key = hivex_value_key (h, values[i]);
413     if (key == NULL) {
414       perrorf (g, "hivex_value_key");
415       goto out;
416     }
417     if (STRCASEEQLEN (key, "\\DosDevices\\", 12) &&
418         c_isalpha (key[12]) && key[13] == ':')
419       count++;
420     free (key);
421   }
422
423   fs->drive_mappings = calloc (2*count + 1, sizeof (char *));
424   if (fs->drive_mappings == NULL) {
425     perrorf (g, "calloc");
426     goto out;
427   }
428
429   for (i = count = 0; values[i] != 0; ++i) {
430     char *key = hivex_value_key (h, values[i]);
431     if (key == NULL) {
432       perrorf (g, "hivex_value_key");
433       goto out;
434     }
435     if (STRCASEEQLEN (key, "\\DosDevices\\", 12) &&
436         c_isalpha (key[12]) && key[13] == ':') {
437       /* Get the binary value.  Is it a fixed disk? */
438       char *blob, *device;
439       size_t len;
440       hive_type type;
441
442       blob = hivex_value_value (h, values[i], &type, &len);
443       if (blob != NULL && type == 3 && len == 12) {
444         /* Try to map the blob to a known disk and partition. */
445         device = map_registry_disk_blob (g, blob);
446         if (device != NULL) {
447           fs->drive_mappings[count++] = safe_strndup (g, &key[12], 1);
448           fs->drive_mappings[count++] = device;
449         }
450       }
451       free (blob);
452     }
453     free (key);
454   }
455
456   /* Get the hostname. */
457   const char *hivepath[] =
458     { fs->windows_current_control_set, "Services", "Tcpip", "Parameters" };
459   for (node = root, i = 0;
460        node != 0 && i < sizeof hivepath / sizeof hivepath[0];
461        ++i) {
462     node = hivex_node_get_child (h, node, hivepath[i]);
463   }
464
465   if (node == 0) {
466     perrorf (g, "hivex: cannot locate HKLM\\SYSTEM\\%s\\Services\\Tcpip\\Parameters",
467              fs->windows_current_control_set);
468     goto out;
469   }
470
471   free (values);
472   values = hivex_node_values (h, node);
473
474   for (i = 0; values[i] != 0; ++i) {
475     char *key = hivex_value_key (h, values[i]);
476     if (key == NULL) {
477       perrorf (g, "hivex_value_key");
478       goto out;
479     }
480
481     if (STRCASEEQ (key, "Hostname")) {
482       fs->hostname = hivex_value_string (h, values[i]);
483       if (!fs->hostname) {
484         perrorf (g, "hivex_value_string");
485         free (key);
486         goto out;
487       }
488     }
489     /* many other interesting fields here ... */
490
491     free (key);
492   }
493
494   ret = 0;
495
496  out:
497   if (h) hivex_close (h);
498   free (values);
499   free (system_path);
500   free (system_hive);
501
502   return ret;
503 }
504
505 /* Windows Registry HKLM\SYSTEM\MountedDevices uses a blob of data
506  * to store partitions.  This blob is described here:
507  * http://www.goodells.net/multiboot/partsigs.shtml
508  * The following function maps this blob to a libguestfs partition
509  * name, if possible.
510  */
511 static char *
512 map_registry_disk_blob (guestfs_h *g, const char *blob)
513 {
514   char **devices = NULL;
515   struct guestfs_partition_list *partitions = NULL;
516   char *diskid;
517   size_t i, j, len;
518   char *ret = NULL;
519   uint64_t part_offset;
520
521   /* First 4 bytes are the disk ID.  Search all devices to find the
522    * disk with this disk ID.
523    */
524   devices = guestfs_list_devices (g);
525   if (devices == NULL)
526     goto out;
527
528   for (i = 0; devices[i] != NULL; ++i) {
529     /* Read the disk ID. */
530     diskid = guestfs_pread_device (g, devices[i], 4, 0x01b8, &len);
531     if (diskid == NULL)
532       continue;
533     if (len < 4) {
534       free (diskid);
535       continue;
536     }
537     if (memcmp (diskid, blob, 4) == 0) { /* found it */
538       free (diskid);
539       goto found_disk;
540     }
541     free (diskid);
542   }
543   goto out;
544
545  found_disk:
546   /* Next 8 bytes are the offset of the partition in bytes(!) given as
547    * a 64 bit little endian number.  Luckily it's easy to get the
548    * partition byte offset from guestfs_part_list.
549    */
550   part_offset = le64toh (* (uint64_t *) &blob[4]);
551
552   partitions = guestfs_part_list (g, devices[i]);
553   if (partitions == NULL)
554     goto out;
555
556   for (j = 0; j < partitions->len; ++j) {
557     if (partitions->val[j].part_start == part_offset) /* found it */
558       goto found_partition;
559   }
560   goto out;
561
562  found_partition:
563   /* Construct the full device name. */
564   ret = safe_asprintf (g, "%s%d", devices[i], partitions->val[j].part_num);
565
566  out:
567   if (devices)
568     guestfs___free_string_list (devices);
569   if (partitions)
570     guestfs_free_partition_list (partitions);
571   return ret;
572 }
573
574 #endif /* defined(HAVE_HIVEX) */
575
576 char *
577 guestfs___case_sensitive_path_silently (guestfs_h *g, const char *path)
578 {
579   guestfs_error_handler_cb old_error_cb = g->error_cb;
580   g->error_cb = NULL;
581   char *ret = guestfs_case_sensitive_path (g, path);
582   g->error_cb = old_error_cb;
583   return ret;
584 }