#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
+#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
+#include <errno.h>
+#include <endian.h>
#ifdef HAVE_PCRE
#include <pcre.h>
static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs);
static int check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs);
static int check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs);
-static char *resolve_windows_path_silently (guestfs_h *g, const char *);
+static char *map_registry_disk_blob (guestfs_h *g, const char *blob);
+static char *case_sensitive_path_silently (guestfs_h *g, const char *);
static int is_file_nocase (guestfs_h *g, const char *);
static int is_dir_nocase (guestfs_h *g, const char *);
static int extend_fses (guestfs_h *g);
static char *resolve_fstab_device (guestfs_h *g, const char *spec);
static void check_package_format (guestfs_h *g, struct inspect_fs *fs);
static void check_package_management (guestfs_h *g, struct inspect_fs *fs);
-static int download_to_tmp (guestfs_h *g, const char *filename, char *localtmp, int64_t max_size);
+static int download_to_tmp (guestfs_h *g, const char *filename, const char *basename, int64_t max_size);
static int inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char *filename, int (*f) (guestfs_h *, struct inspect_fs *));
static char *first_line_of_file (guestfs_h *g, const char *filename);
static int first_egrep_of_file (guestfs_h *g, const char *filename, const char *eregex, int iflag, char **ret);
for (i = 0;
systemroot == NULL && i < sizeof systemroots / sizeof systemroots[0];
++i) {
- systemroot = resolve_windows_path_silently (g, systemroots[i]);
+ systemroot = case_sensitive_path_silently (g, systemroots[i]);
}
if (!systemroot) {
char cmd_exe[len];
snprintf (cmd_exe, len, "%s/system32/cmd.exe", fs->windows_systemroot);
- char *cmd_exe_path = resolve_windows_path_silently (g, cmd_exe);
+ char *cmd_exe_path = case_sensitive_path_silently (g, cmd_exe);
if (!cmd_exe_path)
return 0;
static int
check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs)
{
- TMP_TEMPLATE_ON_STACK (software_local);
+ const char *basename = "software";
+ char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
+ snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
+ g->tmpdir, basename);
size_t len = strlen (fs->windows_systemroot) + 64;
char software[len];
snprintf (software, len, "%s/system32/config/software",
fs->windows_systemroot);
- char *software_path = resolve_windows_path_silently (g, software);
+ char *software_path = case_sensitive_path_silently (g, software);
if (!software_path)
/* If the software hive doesn't exist, just accept that we cannot
* find product_name etc.
hive_h *h = NULL;
hive_value_h *values = NULL;
- if (download_to_tmp (g, software_path, software_local,
- MAX_REGISTRY_SIZE) == -1)
+ if (download_to_tmp (g, software_path, basename, MAX_REGISTRY_SIZE) == -1)
goto out;
- h = hivex_open (software_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
+ h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
if (h == NULL) {
perrorf (g, "hivex_open");
goto out;
free (values);
free (software_path);
- /* Free up the temporary file. */
- unlink (software_local);
-#undef software_local_len
-
return ret;
}
static int
check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs)
{
- TMP_TEMPLATE_ON_STACK (system_local);
+ const char *basename = "system";
+ char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
+ snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
+ g->tmpdir, basename);
size_t len = strlen (fs->windows_systemroot) + 64;
char system[len];
snprintf (system, len, "%s/system32/config/system",
fs->windows_systemroot);
- char *system_path = resolve_windows_path_silently (g, system);
+ char *system_path = case_sensitive_path_silently (g, system);
if (!system_path)
/* If the system hive doesn't exist, just accept that we cannot
* find hostname etc.
int ret = -1;
hive_h *h = NULL;
- hive_value_h *values = NULL;
+ hive_node_h root, node;
+ hive_value_h value, *values = NULL;
+ int32_t dword;
+ size_t i, count;
- if (download_to_tmp (g, system_path, system_local, MAX_REGISTRY_SIZE) == -1)
+ if (download_to_tmp (g, system_path, basename, MAX_REGISTRY_SIZE) == -1)
goto out;
- h = hivex_open (system_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
+ h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
if (h == NULL) {
perrorf (g, "hivex_open");
goto out;
}
- hive_node_h node = hivex_root (h);
- /* XXX Don't hard-code ControlSet001. The current control set would
- * be another good thing to expose up through the inspection API.
+ root = hivex_root (h);
+ if (root == 0) {
+ perrorf (g, "hivex_root");
+ goto out;
+ }
+
+ /* Get the CurrentControlSet. */
+ errno = 0;
+ node = hivex_node_get_child (h, root, "Select");
+ if (node == 0) {
+ if (errno != 0)
+ perrorf (g, "hivex_node_get_child");
+ else
+ error (g, "hivex: could not locate HKLM\\SYSTEM\\Select");
+ goto out;
+ }
+
+ errno = 0;
+ value = hivex_node_get_value (h, node, "Current");
+ if (value == 0) {
+ if (errno != 0)
+ perrorf (g, "hivex_node_get_value");
+ else
+ error (g, "hivex: HKLM\\System\\Select Default entry not found.");
+ goto out;
+ }
+
+ /* XXX Should check the type. */
+ dword = hivex_value_dword (h, value);
+ fs->windows_current_control_set = safe_asprintf (g, "ControlSet%03d", dword);
+
+ /* Get the drive mappings.
+ * This page explains the contents of HKLM\System\MountedDevices:
+ * http://www.goodells.net/multiboot/partsigs.shtml
*/
+ errno = 0;
+ node = hivex_node_get_child (h, root, "MountedDevices");
+ if (node == 0) {
+ if (errno != 0)
+ perrorf (g, "hivex_node_get_child");
+ else
+ error (g, "hivex: could not locate HKLM\\SYSTEM\\MountedDevices");
+ goto out;
+ }
+
+ values = hivex_node_values (h, node);
+
+ /* Count how many DOS drive letter mappings there are. This doesn't
+ * ignore removable devices, so it overestimates, but that doesn't
+ * matter because it just means we'll allocate a few bytes extra.
+ */
+ for (i = count = 0; values[i] != 0; ++i) {
+ char *key = hivex_value_key (h, values[i]);
+ if (key == NULL) {
+ perrorf (g, "hivex_value_key");
+ goto out;
+ }
+ if (STRCASEEQLEN (key, "\\DosDevices\\", 12) &&
+ c_isalpha (key[12]) && key[13] == ':')
+ count++;
+ free (key);
+ }
+
+ fs->drive_mappings = calloc (2*count + 1, sizeof (char *));
+ if (fs->drive_mappings == NULL) {
+ perrorf (g, "calloc");
+ goto out;
+ }
+
+ for (i = count = 0; values[i] != 0; ++i) {
+ char *key = hivex_value_key (h, values[i]);
+ if (key == NULL) {
+ perrorf (g, "hivex_value_key");
+ goto out;
+ }
+ if (STRCASEEQLEN (key, "\\DosDevices\\", 12) &&
+ c_isalpha (key[12]) && key[13] == ':') {
+ /* Get the binary value. Is it a fixed disk? */
+ char *blob, *device;
+ size_t len;
+ hive_type type;
+
+ blob = hivex_value_value (h, values[i], &type, &len);
+ if (blob != NULL && type == 3 && len == 12) {
+ /* Try to map the blob to a known disk and partition. */
+ device = map_registry_disk_blob (g, blob);
+ if (device != NULL) {
+ fs->drive_mappings[count++] = safe_strndup (g, &key[12], 1);
+ fs->drive_mappings[count++] = device;
+ }
+ }
+ free (blob);
+ }
+ free (key);
+ }
+
+ /* Get the hostname. */
const char *hivepath[] =
- { "ControlSet001", "Services", "Tcpip", "Parameters" };
- size_t i;
- for (i = 0;
+ { fs->windows_current_control_set, "Services", "Tcpip", "Parameters" };
+ for (node = root, i = 0;
node != 0 && i < sizeof hivepath / sizeof hivepath[0];
++i) {
node = hivex_node_get_child (h, node, hivepath[i]);
}
if (node == 0) {
- perrorf (g, "hivex: cannot locate HKLM\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters");
+ perrorf (g, "hivex: cannot locate HKLM\\SYSTEM\\%s\\Services\\Tcpip\\Parameters",
+ fs->windows_current_control_set);
goto out;
}
+ free (values);
values = hivex_node_values (h, node);
for (i = 0; values[i] != 0; ++i) {
free (values);
free (system_path);
- /* Free up the temporary file. */
- unlink (system_local);
-#undef system_local_len
+ return ret;
+}
+
+/* Windows Registry HKLM\SYSTEM\MountedDevices uses a blob of data
+ * to store partitions. This blob is described here:
+ * http://www.goodells.net/multiboot/partsigs.shtml
+ * The following function maps this blob to a libguestfs partition
+ * name, if possible.
+ */
+static char *
+map_registry_disk_blob (guestfs_h *g, const char *blob)
+{
+ char **devices = NULL;
+ struct guestfs_partition_list *partitions = NULL;
+ char *diskid;
+ size_t i, j, len;
+ char *ret = NULL;
+ uint64_t part_offset;
+
+ /* First 4 bytes are the disk ID. Search all devices to find the
+ * disk with this disk ID.
+ */
+ devices = guestfs_list_devices (g);
+ if (devices == NULL)
+ goto out;
+
+ for (i = 0; devices[i] != NULL; ++i) {
+ /* Read the disk ID. */
+ diskid = guestfs_pread_device (g, devices[i], 4, 0x01b8, &len);
+ if (diskid == NULL)
+ continue;
+ if (len < 4) {
+ free (diskid);
+ continue;
+ }
+ if (memcmp (diskid, blob, 4) == 0) { /* found it */
+ free (diskid);
+ goto found_disk;
+ }
+ free (diskid);
+ }
+ goto out;
+
+ found_disk:
+ /* Next 8 bytes are the offset of the partition in bytes(!) given as
+ * a 64 bit little endian number. Luckily it's easy to get the
+ * partition byte offset from guestfs_part_list.
+ */
+ part_offset = le64toh (* (uint64_t *) &blob[4]);
+
+ partitions = guestfs_part_list (g, devices[i]);
+ if (partitions == NULL)
+ goto out;
+ for (j = 0; j < partitions->len; ++j) {
+ if (partitions->val[j].part_start == part_offset) /* found it */
+ goto found_partition;
+ }
+ goto out;
+
+ found_partition:
+ /* Construct the full device name. */
+ ret = safe_asprintf (g, "%s%d", devices[i], partitions->val[j].part_num);
+
+ out:
+ if (devices)
+ guestfs___free_string_list (devices);
+ if (partitions)
+ guestfs_free_partition_list (partitions);
return ret;
}
static char *
-resolve_windows_path_silently (guestfs_h *g, const char *path)
+case_sensitive_path_silently (guestfs_h *g, const char *path)
{
guestfs_error_handler_cb old_error_cb = g->error_cb;
g->error_cb = NULL;
char *p;
int r;
- p = resolve_windows_path_silently (g, path);
+ p = case_sensitive_path_silently (g, path);
if (!p)
return 0;
r = guestfs_is_file (g, p);
char *p;
int r;
- p = resolve_windows_path_silently (g, path);
+ p = case_sensitive_path_silently (g, path);
if (!p)
return 0;
r = guestfs_is_dir (g, p);
}
char *
+guestfs__inspect_get_windows_current_control_set (guestfs_h *g,
+ const char *root)
+{
+ struct inspect_fs *fs = search_for_root (g, root);
+ if (!fs)
+ return NULL;
+
+ if (!fs->windows_current_control_set) {
+ error (g, _("not a Windows guest, or CurrentControlSet could not be determined"));
+ return NULL;
+ }
+
+ return safe_strdup (g, fs->windows_current_control_set);
+}
+
+char *
guestfs__inspect_get_format (guestfs_h *g, const char *root)
{
struct inspect_fs *fs = search_for_root (g, root);
return ret;
}
+char **
+guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root)
+{
+ char **ret;
+ size_t i, count;
+ struct inspect_fs *fs;
+
+ fs = search_for_root (g, root);
+ if (!fs)
+ return NULL;
+
+ /* If no drive mappings, return an empty hashtable. */
+ if (!fs->drive_mappings)
+ count = 0;
+ else {
+ for (count = 0; fs->drive_mappings[count] != NULL; count++)
+ ;
+ }
+
+ ret = calloc (count+1, sizeof (char *));
+ if (ret == NULL) {
+ perrorf (g, "calloc");
+ return NULL;
+ }
+
+ /* We need to make a deep copy of the hashtable since the caller
+ * will free it.
+ */
+ for (i = 0; i < count; ++i)
+ ret[i] = safe_strdup (g, fs->drive_mappings[i]);
+
+ ret[count] = NULL;
+
+ return ret;
+}
+
char *
guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
{
static struct guestfs_application_list *
list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
{
- TMP_TEMPLATE_ON_STACK (tmpfile);
+ const char *basename = "rpm_Name";
+ char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
+ snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
+ g->tmpdir, basename);
- if (download_to_tmp (g, "/var/lib/rpm/Name", tmpfile, MAX_PKG_DB_SIZE) == -1)
+ if (download_to_tmp (g, "/var/lib/rpm/Name", basename, MAX_PKG_DB_SIZE) == -1)
return NULL;
struct guestfs_application_list *apps = NULL, *ret = NULL;
-#define cmd_len (strlen (tmpfile) + 64)
+#define cmd_len (strlen (tmpdir_basename) + 64)
char cmd[cmd_len];
FILE *pp = NULL;
char line[1024];
size_t len;
- snprintf (cmd, cmd_len, DB_DUMP " -p '%s'", tmpfile);
+ snprintf (cmd, cmd_len, DB_DUMP " -p '%s'", tmpdir_basename);
debug (g, "list_applications_rpm: %s", cmd);
guestfs_free_application_list (apps);
if (pp)
pclose (pp);
- unlink (tmpfile);
-#undef cmd_len
return ret;
}
static struct guestfs_application_list *
list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
{
- TMP_TEMPLATE_ON_STACK (tmpfile);
+ const char *basename = "deb_status";
+ char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
+ snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
+ g->tmpdir, basename);
- if (download_to_tmp (g, "/var/lib/dpkg/status", tmpfile,
+ if (download_to_tmp (g, "/var/lib/dpkg/status", basename,
MAX_PKG_DB_SIZE) == -1)
return NULL;
char *name = NULL, *version = NULL, *release = NULL;
int installed_flag = 0;
- fp = fopen (tmpfile, "r");
+ fp = fopen (tmpdir_basename, "r");
if (fp == NULL) {
- perrorf (g, "fopen: %s", tmpfile);
+ perrorf (g, "fopen: %s", tmpdir_basename);
goto out;
}
}
if (fclose (fp) == -1) {
- perrorf (g, "fclose: %s", tmpfile);
+ perrorf (g, "fclose: %s", tmpdir_basename);
goto out;
}
fp = NULL;
free (name);
free (version);
free (release);
- unlink (tmpfile);
return ret;
}
static struct guestfs_application_list *
list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
{
- TMP_TEMPLATE_ON_STACK (software_local);
+ const char *basename = "software";
+ char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2];
+ snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s",
+ g->tmpdir, basename);
- /* XXX We already download the SOFTWARE hive when doing general
- * inspection. We could avoid this second download of the same file
- * by caching these entries in the handle.
- */
size_t len = strlen (fs->windows_systemroot) + 64;
char software[len];
snprintf (software, len, "%s/system32/config/software",
fs->windows_systemroot);
- char *software_path = resolve_windows_path_silently (g, software);
+ char *software_path = case_sensitive_path_silently (g, software);
if (!software_path)
/* If the software hive doesn't exist, just accept that we cannot
- * find product_name etc.
+ * list windows apps.
*/
return 0;
struct guestfs_application_list *ret = NULL;
hive_h *h = NULL;
- if (download_to_tmp (g, software_path, software_local,
- MAX_REGISTRY_SIZE) == -1)
+ if (download_to_tmp (g, software_path, basename, MAX_REGISTRY_SIZE) == -1)
goto out;
free (software_path);
software_path = NULL;
- h = hivex_open (software_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
+ h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
if (h == NULL) {
perrorf (g, "hivex_open");
goto out;
if (h) hivex_close (h);
free (software_path);
- /* Delete the temporary file. */
- unlink (software_local);
-#undef software_local_len
-
return ret;
}
compare_applications);
}
-/* Download to a guest file to a local temporary file. Refuse to
+/* Download a guest file to a local temporary file. The file is
+ * downloaded into g->tmpdir, unless it already exists in g->tmpdir.
+ * The final name will be g->tmpdir + "/" + basename. Refuse to
* download the guest file if it is larger than max_size. The caller
- * is responsible for deleting the temporary file after use.
+ * does not need to delete the temporary file after use: it will be
+ * deleted when the handle is cleaned up.
*/
static int
download_to_tmp (guestfs_h *g, const char *filename,
- char *localtmp, int64_t max_size)
+ const char *basename, int64_t max_size)
{
- int fd;
+ int tmpdirfd, fd, r = -1;
char buf[32];
int64_t size;
+ tmpdirfd = open (g->tmpdir, O_RDONLY);
+ if (tmpdirfd == -1) {
+ perrorf (g, _("%s: temporary directory not found"), g->tmpdir);
+ return -1;
+ }
+
+ /* If the file has already been downloaded, return. */
+ if (faccessat (tmpdirfd, basename, R_OK, 0) == 0) {
+ r = 0;
+ goto out;
+ }
+
+ /* Check size of remote file. */
size = guestfs_filesize (g, filename);
if (size == -1)
/* guestfs_filesize failed and has already set error in handle */
- return -1;
+ goto out;
if (size > max_size) {
error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
filename, size);
- return -1;
+ goto out;
}
- fd = mkstemp (localtmp);
+ fd = openat (tmpdirfd, basename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0600);
if (fd == -1) {
- perrorf (g, "mkstemp");
- return -1;
+ perrorf (g, "openat: %s/%s", g->tmpdir, basename);
+ goto out;
}
snprintf (buf, sizeof buf, "/dev/fd/%d", fd);
if (guestfs_download (g, filename, buf) == -1) {
+ unlinkat (tmpdirfd, basename, 0);
close (fd);
- unlink (localtmp);
- return -1;
+ goto out;
}
if (close (fd) == -1) {
- perrorf (g, "close: %s", localtmp);
- unlink (localtmp);
- return -1;
+ perrorf (g, "close: %s/%s", g->tmpdir, basename);
+ unlinkat (tmpdirfd, basename, 0);
+ goto out;
}
- return 0;
+ r = 0;
+ out:
+ if (tmpdirfd >= 0)
+ close (tmpdirfd);
+
+ return r;
}
/* Call 'f' with Augeas opened and having parsed 'filename' (this file
NOT_IMPL(NULL);
}
+char *
+guestfs__inspect_get_windows_current_control_set (guestfs_h *g,
+ const char *root)
+{
+ NOT_IMPL(NULL);
+}
+
char **
guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root)
{
NOT_IMPL(NULL);
}
+char **
+guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root)
+{
+ NOT_IMPL(NULL);
+}
+
char *
guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
{
free (g->fses[i].arch);
free (g->fses[i].hostname);
free (g->fses[i].windows_systemroot);
+ free (g->fses[i].windows_current_control_set);
size_t j;
for (j = 0; j < g->fses[i].nr_fstab; ++j) {
free (g->fses[i].fstab[j].device);
free (g->fses[i].fstab[j].mountpoint);
}
free (g->fses[i].fstab);
+ if (g->fses[i].drive_mappings)
+ guestfs___free_string_list (g->fses[i].drive_mappings);
}
free (g->fses);
g->nr_fses = 0;