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