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