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