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