Version 0.1.2. 0.1.2
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 16 Dec 2010 10:43:19 +0000 (10:43 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 16 Dec 2010 15:49:08 +0000 (15:49 +0000)
16 files changed:
.depend
Makefile.am
TODO
configure.ac
filetree.ml
filetree.mli
filetree_markup.ml [new file with mode: 0644]
filetree_markup.mli [new file with mode: 0644]
filetree_ops.ml
filetree_type.ml
filetree_type.mli
guestfs-browser.spec.in
slave.ml
slave.mli
utils.ml
utils.mli

diff --git a/.depend b/.depend
index 4df490d..a2f2dac 100644 (file)
--- a/.depend
+++ b/.depend
@@ -8,11 +8,14 @@ deviceSet.cmi:
 deviceSet.cmo: deviceSet.cmi 
 deviceSet.cmx: deviceSet.cmi 
 filetree.cmi: slave.cmi 
-filetree.cmo: utils.cmi slave.cmi filetree_type.cmi filetree_ops.cmi deviceSet.cmi filetree.cmi 
-filetree.cmx: utils.cmx slave.cmx filetree_type.cmx filetree_ops.cmx deviceSet.cmx filetree.cmi 
+filetree.cmo: utils.cmi slave.cmi filetree_type.cmi filetree_ops.cmi filetree_markup.cmi deviceSet.cmi filetree.cmi 
+filetree.cmx: utils.cmx slave.cmx filetree_type.cmx filetree_ops.cmx filetree_markup.cmx deviceSet.cmx filetree.cmi 
+filetree_markup.cmi: slave.cmi filetree_type.cmi 
+filetree_markup.cmo: utils.cmi slave.cmi filetree_type.cmi filetree_markup.cmi 
+filetree_markup.cmx: utils.cmx slave.cmx filetree_type.cmx filetree_markup.cmi 
 filetree_ops.cmi: slave.cmi filetree_type.cmi 
-filetree_ops.cmo: utils.cmi slave.cmi filetree_type.cmi filetree_ops.cmi 
-filetree_ops.cmx: utils.cmx slave.cmx filetree_type.cmx filetree_ops.cmi 
+filetree_ops.cmo: utils.cmi slave.cmi filetree_type.cmi filetree_markup.cmi filetree_ops.cmi 
+filetree_ops.cmx: utils.cmx slave.cmx filetree_type.cmx filetree_markup.cmx filetree_ops.cmi 
 filetree_type.cmi: slave.cmi 
 filetree_type.cmo: utils.cmi slave.cmi filetree_type.cmi 
 filetree_type.cmx: utils.cmx slave.cmx filetree_type.cmi 
index 57023f2..8a1a8d9 100644 (file)
@@ -38,6 +38,8 @@ SOURCES = \
        deviceSet.ml \
        filetree.mli \
        filetree.ml \
+       filetree_markup.mli \
+       filetree_markup.ml \
        filetree_ops.mli \
        filetree_ops.ml \
        filetree_type.mli \
@@ -60,6 +62,7 @@ OBJECTS = \
        deviceSet.cmx \
        slave.cmx \
        filetree_type.cmx \
+       filetree_markup.cmx \
        filetree_ops.cmx \
        filetree.cmx \
        window.cmx \
@@ -67,7 +70,8 @@ OBJECTS = \
 
 bin_SCRIPTS = guestfs-browser
 
-OCAMLPACKAGES = libvirt,guestfs,lablgtk2,extlib,xml-light,threads
+OCAMLPACKAGES = \
+       libvirt,guestfs,hivex,lablgtk2,extlib,xml-light,camomile,threads
 OCAMLCFLAGS = \
        -g \
        -warn-error CDEFLMPSUVYZX \
diff --git a/TODO b/TODO
index 1e4a09c..d872f12 100644 (file)
--- a/TODO
+++ b/TODO
@@ -14,10 +14,10 @@ x  Device checksum (slow?)
 ?  LV information
 ?  Ext2 superblock info (tune2fs)
 
-Display Windows Registry as a separate tree.
-
 The slave thread should not have to remount filesystems.
 If the mount points are the same as the previous command, it
 should cache them.
 
 About dialog
+
+Extended attributes, SELinux.
index 14c3bc7..d1853ab 100644 (file)
@@ -15,7 +15,7 @@ dnl You should have received a copy of the GNU General Public License along
 dnl with this program; if not, write to the Free Software Foundation, Inc.,
 dnl 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-AC_INIT([guestfs-browser],[0.1.1])
+AC_INIT([guestfs-browser],[0.1.2])
 AM_INIT_AUTOMAKE([foreign])
 AC_CONFIG_MACRO_DIR([m4])
 
@@ -58,11 +58,21 @@ if test "$OCAML_PKG_guestfs" = "no"; then
     AC_MSG_ERROR([Please install OCaml module 'guestfs'.])
 fi
 
+AC_CHECK_OCAML_PKG([hivex])
+if test "$OCAML_PKG_hivex" = "no"; then
+    AC_MSG_ERROR([Please install OCaml module 'hivex'.])
+fi
+
 AC_CHECK_OCAML_PKG([xml-light])
 if test "$OCAML_PKG_xml_light" = "no"; then
     AC_MSG_ERROR([Please install OCaml module 'xml-light'.])
 fi
 
+AC_CHECK_OCAML_PKG([camomile])
+if test "$OCAML_PKG_camomile" = "no"; then
+    AC_MSG_ERROR([Please install OCaml module 'camomile'.])
+fi
+
 AC_CHECK_OCAML_PKG([extlib])
 if test "$OCAML_PKG_extlib" = "no"; then
     AC_MSG_ERROR([Please install OCaml module 'extlib'.])
index f252f2a..a007d97 100644 (file)
@@ -25,12 +25,18 @@ open Utils
 open DeviceSet
 
 open Filetree_type
+open Filetree_markup
 open Filetree_ops
 
 module G = Guestfs
 
 type t = Filetree_type.t
 
+(* Temporary directory for shared use by all instances of this widget,
+ * cleaned up when the program exits.
+ *)
+let tmpdir = tmpdir ()
+
 let rec create ~packing () =
   let view = GTree.view ~packing () in
   (*view#set_rules_hint true;*)
@@ -70,6 +76,7 @@ let rec create ~packing () =
   let renderer = GTree.cell_renderer_text [], ["markup", name_col] in
   let name_view = GTree.view_column ~title:"Filename" ~renderer () in
   name_view#set_resizable true;
+  name_view#set_sizing `AUTOSIZE;
   ignore (view#append_column name_view);
 
   let renderer = GTree.cell_renderer_text [`XALIGN 1.], ["markup", size_col] in
@@ -140,7 +147,9 @@ and button_press ({ model = model; view = view } as t) ev =
           let hdata = get_hdata t row in
           match hdata with
           | { content=(Loading | ErrorMessage _ | Info _) } -> None
-          | { content=(Top _ | Directory _ | File _) } -> Some (path, hdata)
+          | { content=(Top _ | Directory _ | File _ |
+                           TopWinReg _ | RegKey _ | RegValue _ ) } ->
+              Some (path, hdata)
       ) paths in
 
     (* Based on number of selected rows and what is selected, construct
@@ -234,75 +243,11 @@ and make_context_menu t paths =
 
   menu
 
-(* Mark up mode. *)
-let markup_of_mode mode =
-  let c =
-    if is_socket mode then 's'
-    else if is_symlink mode then 'l'
-    else if is_regular_file mode then '-'
-    else if is_block mode then 'b'
-    else if is_directory mode then 'd'
-    else if is_char mode then 'c'
-    else if is_fifo mode then 'p' else '?' in
-  let ru = if is_ru mode then 'r' else '-' in
-  let wu = if is_wu mode then 'w' else '-' in
-  let xu = if is_xu mode then 'x' else '-' in
-  let rg = if is_rg mode then 'r' else '-' in
-  let wg = if is_wg mode then 'w' else '-' in
-  let xg = if is_xg mode then 'x' else '-' in
-  let ro = if is_ro mode then 'r' else '-' in
-  let wo = if is_wo mode then 'w' else '-' in
-  let xo = if is_xo mode then 'x' else '-' in
-  let str = sprintf "%c%c%c%c%c%c%c%c%c%c" c ru wu xu rg wg xg ro wo xo in
-
-  let suid = is_suid mode in
-  let sgid = is_sgid mode in
-  let svtx = is_svtx mode in
-  if suid then str.[3] <- 's';
-  if sgid then str.[6] <- 's';
-  if svtx then str.[9] <- 't';
-
-  "<span color=\"#222222\" size=\"small\">" ^ str ^ "</span>"
-
-(* Mark up dates. *)
-let markup_of_date t =
-  (* Guestfs gives us int64's, we want float which is OCaml's
-   * equivalent of time_t.
-   *)
-  let t = Int64.to_float t in
-
-  let show_full_date () =
-    let tm = localtime t in
-    sprintf "<span color=\"#222222\" size=\"small\">%04d-%02d-%02d %02d:%02d:%02d</span>"
-      (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
-      tm.tm_hour tm.tm_min tm.tm_sec
-  in
-
-  (* How long ago? *)
-  let now = time () in
-  let ago = now -. t in
-  if ago < 0. then (* future *)
-    show_full_date ()
-  else if ago < 60. then
-    "<small>now</small>"
-  else if ago < 60. *. 60. then
-    sprintf "<small>%.0f minutes ago</small>" (ago /. 60.)
-  else if ago < 60. *. 60. *. 24. then
-    sprintf "<small>%.0f hours ago</small>" (ago /. 60. /. 60.)
-  else if ago < 60. *. 60. *. 24. *. 28. then
-    sprintf "<small>%.0f days ago</small>" (ago /. 60. /. 60. /. 24.)
-  else
-    show_full_date ()
-
-(* Mark up file sizes. *)
-let markup_of_size bytes =
-  sprintf "<small>%s</small>" (human_size bytes)
-
 let clear { model = model; hash = hash } =
   model#clear ();
   Hashtbl.clear hash
 
-let rec add ({ model = model; hash = hash } as t) name data =
+let rec add ({ model = model } as t) name data =
   clear t;
 
   (* Populate the top level of the filetree.  If there are operating
@@ -324,51 +269,103 @@ let rec add ({ model = model; hash = hash } as t) name data =
   (* Add top level left-over filesystems. *)
   DeviceSet.iter (add_top_level_vol t name) other_filesystems;
 
+  (* If it's Windows and registry files exist, create a node for
+   * each file.
+   *)
+  List.iter (
+    fun os ->
+      (match os.Slave.insp_winreg_SAM with
+       | Some filename ->
+           add_top_level_winreg t name os "HKEY_LOCAL_MACHINE\\SAM" filename
+       | None -> ()
+      );
+      (match os.Slave.insp_winreg_SECURITY with
+       | Some filename ->
+           add_top_level_winreg t name os "HKEY_LOCAL_MACHINE\\SECURITY"
+             filename
+       | None -> ()
+      );
+      (match os.Slave.insp_winreg_SOFTWARE with
+       | Some filename ->
+           add_top_level_winreg t name os "HKEY_LOCAL_MACHINE\\SOFTWARE"
+             filename
+       | None -> ()
+      );
+      (match os.Slave.insp_winreg_SYSTEM with
+       | Some filename ->
+           add_top_level_winreg t name os "HKEY_LOCAL_MACHINE\\SYSTEM"
+             filename
+       | None -> ()
+      );
+      (match os.Slave.insp_winreg_DEFAULT with
+       | Some filename ->
+           add_top_level_winreg t name os "HKEY_USERS\\.DEFAULT" filename
+       | None -> ()
+      );
+  ) data.Slave.insp_oses;
+
   (* Expand the first top level node. *)
   match model#get_iter_first with
   | None -> ()
   | Some row ->
       t.view#expand_row (model#get_path row)
 
-and add_top_level_os ({ model = model; hash = hash } as t) name os =
+(* Add a top level operating system node. *)
+and add_top_level_os ({ model = model } as t) name os =
   let markup =
     sprintf "<b>%s</b>\n<small>%s</small>\n<small>%s</small>"
       (markup_escape name) (markup_escape os.Slave.insp_hostname)
       (markup_escape os.Slave.insp_product_name) in
 
   let row = model#append () in
-  make_node t row (Top (Slave.OS os));
+  make_node t row (Top (Slave.OS os)) None;
   model#set ~row ~column:t.name_col markup
 
-and add_top_level_vol ({ model = model; hash = hash } as t) name dev =
+(* Add a top level volume (left over filesystem) node. *)
+and add_top_level_vol ({ model = model } as t) name dev =
   let markup =
     sprintf "<b>%s</b>\n<small>from %s</small>"
       (markup_escape dev) (markup_escape name) in
 
   let row = model#append () in
-  make_node t row (Top (Slave.Volume dev));
+  make_node t row (Top (Slave.Volume dev)) None;
+  model#set ~row ~column:t.name_col markup
+
+(* Add a top level Windows Registry node. *)
+and add_top_level_winreg ({ model = model } as t) name os rootkey
+    remotefile =
+  let cachefile = tmpdir // string_of_int (unique ()) ^ ".hive" in
+
+  let markup =
+    sprintf "<b>%s</b>\n<small>from %s</small>"
+      (markup_escape rootkey) (markup_escape name) in
+
+  let row = model#append () in
+  make_node t row
+    (TopWinReg (Slave.OS os, rootkey, remotefile, cachefile)) None;
   model#set ~row ~column:t.name_col markup
 
 (* Generic function to make an openable node to the tree. *)
-and make_node ({ model = model; hash = hash } as t) row content =
-  let hdata = { state=NodeNotStarted; content=content; visited=false } in
+and make_node ({ model = model } as t) row content hiveh =
+  let hdata =
+    { state=NodeNotStarted; content=content; visited=false; hiveh=hiveh } in
   store_hdata t row hdata;
 
   (* Create a placeholder "loading ..." row underneath this node so
    * the user has something to expand.
    *)
   let placeholder = model#append ~parent:row () in
-  let hdata = { state=IsLeaf; content=Loading; visited=false } in
+  let hdata = { state=IsLeaf; content=Loading; visited=false; hiveh=None } in
   store_hdata t placeholder hdata;
   model#set ~row:placeholder ~column:t.name_col "<i>Loading ...</i>";
   ignore (t.view#connect#row_expanded ~callback:(expand_row t))
 
-and make_leaf ({ model = model; hash = hash } as t) row content =
-  let hdata = { state=IsLeaf; content=content; visited=false } in
+and make_leaf ({ model = model } as t) row content hiveh =
+  let hdata = { state=IsLeaf; content=content; visited=false; hiveh=hiveh } in
   store_hdata t row hdata
 
 (* This is called when the user expands a row. *)
-and expand_row ({ model = model; hash = hash } as t) row _ =
+and expand_row ({ model = model } as t) row _ =
   match get_hdata t row with
   | { state=NodeNotStarted; content=Top src } as hdata ->
       (* User has opened a top level node that was not previously opened. *)
@@ -396,10 +393,37 @@ and expand_row ({ model = model; hash = hash } as t) row _ =
       Slave.read_directory ~fail:(when_read_directory_fail t path)
         src pathname (when_read_directory t path)
 
+  | { state=NodeNotStarted;
+      content=TopWinReg (src, rootkey, remotefile, cachefile) } as hdata ->
+      (* User has opened a Windows Registry top level node
+       * not previously opened.
+       *)
+
+      (* Mark this row as loading. *)
+      hdata.state <- NodeLoading;
+
+      (* Get a stable path for this row. *)
+      let path = model#get_path row in
+
+      (* Since the user has opened this top level registry node for the
+       * first time, we now need to download the hive.
+       *)
+      Slave.download_file ~fail:(when_downloaded_registry_fail t path)
+        src remotefile cachefile (when_downloaded_registry t path)
+
+  | { state=NodeNotStarted; content=RegKey node } as hdata ->
+      (* User has opened a Windows Registry key node not previously opened. *)
+
+      (* Mark this row as loading. *)
+      hdata.state <- NodeLoading;
+
+      expand_hive_node t row node
+
+  (* Ignore when a user opens a node which is loading or has been loaded. *)
   | { state=(NodeLoading|IsNode) } -> ()
 
   (* These are not nodes so it should never be possible to open them. *)
-  | { content=File _ } | { state=IsLeaf } -> assert false
+  | { content=(File _ | RegValue _) } | { state=IsLeaf } -> assert false
 
   (* Node should not exist in the tree. *)
   | { state=NodeNotStarted; content=(Loading | ErrorMessage _ | Info _) } ->
@@ -418,9 +442,9 @@ and when_read_directory ({ model = model } as t) path entries =
         direntry in
       let row = model#append ~parent:row () in
       if is_directory stat.G.mode then
-        make_node t row (Directory direntry)
+        make_node t row (Directory direntry) None
       else
-        make_leaf t row (File direntry);
+        make_leaf t row (File direntry) None;
       model#set ~row ~column:t.name_col (markup_of_name direntry);
       model#set ~row ~column:t.mode_col (markup_of_mode stat.G.mode);
       model#set ~row ~column:t.size_col (markup_of_size stat.G.size);
@@ -455,7 +479,8 @@ and when_read_directory_fail ({ model = model } as t) path exn =
       let row = model#get_iter path in
       let row = model#iter_children ~nth:0 (Some row) in
 
-      let hdata = { state=IsLeaf; content=ErrorMessage msg; visited=false } in
+      let hdata =
+        { state=IsLeaf; content=ErrorMessage msg; visited=false; hiveh=None } in
       store_hdata t row hdata;
 
       model#set ~row ~column:t.name_col (markup_escape msg)
@@ -463,3 +488,91 @@ and when_read_directory_fail ({ model = model } as t) path exn =
   | exn ->
       (* unexpected exception: re-raise it *)
       raise exn
+
+(* Called when the top level registry node has been opened and the
+ * hive file was downloaded to the cache file successfully.
+ *)
+and when_downloaded_registry ({ model = model } as t) path () =
+  debug "when_downloaded_registry";
+  let row = model#get_iter path in
+
+  let hdata = get_hdata t row in
+  match hdata.content with
+  | TopWinReg (src, rootkey, remotefile, cachefile) ->
+      (try
+         (* Open the hive and save the hive handle in the row hdata. *)
+         let flags = if verbose () then [ Hivex.OPEN_VERBOSE ] else [] in
+         let h = Hivex.open_file cachefile flags in
+         hdata.hiveh <- Some h;
+
+         (* Continue as if expanding any other hive node. *)
+         let root = Hivex.root h in
+         expand_hive_node t row root
+       with
+         Hivex.Error _ as exn -> when_downloaded_registry_fail t path exn
+      )
+  | _ -> assert false
+
+(* Called instead of {!when_downloaded_registry} if the download failed. *)
+and when_downloaded_registry_fail ({ model = model } as t) path exn =
+  debug "when_downloaded_registry_fail: %s" (Printexc.to_string exn);
+
+  match exn with
+  | G.Error msg
+  | Hivex.Error (_, _, msg) ->
+      let row = model#get_iter path in
+      let row = model#iter_children ~nth:0 (Some row) in
+
+      let hdata =
+        { state=IsLeaf; content=ErrorMessage msg; visited=false; hiveh=None } in
+      store_hdata t row hdata;
+
+      model#set ~row ~column:t.name_col (markup_escape msg)
+
+  | exn ->
+      (* unexpected exception: re-raise it *)
+      raise exn
+
+(* Expand a hive node. *)
+and expand_hive_node ({ model = model } as t) row node =
+  debug "expand_hive_node";
+  let hdata = get_hdata t row in
+  let h = Option.get hdata.hiveh in
+
+  (* Read the hive entries (values, subkeys) at this node and add them
+   * to the tree.
+   *)
+  let values = Hivex.node_values h node in
+  let cmp v1 v2 = compare (Hivex.value_key h v1) (Hivex.value_key h v2) in
+  Array.sort cmp values;
+  Array.iter (
+    fun value ->
+      let row = model#append ~parent:row () in
+      make_leaf t row (RegValue value) (Some h);
+      model#set ~row ~column:t.name_col (markup_of_regvalue h value);
+      model#set ~row ~column:t.size_col (markup_of_regvaluesize h value);
+      model#set ~row ~column:t.date_col (markup_of_regvaluetype h value);
+  ) values;
+
+  let children = Hivex.node_children h node in
+  let cmp n1 n2 = compare (Hivex.node_name h n1) (Hivex.node_name h n2) in
+  Array.sort cmp children;
+  Array.iter (
+    fun node ->
+      let row = model#append ~parent:row () in
+      make_node t row (RegKey node) (Some h);
+      model#set ~row ~column:t.name_col (markup_of_regkey h node);
+  ) children;
+
+  (* Remove the placeholder "Loading" entry.  NB. Must be done AFTER
+   * adding the other entries, or else Gtk will unexpand the row.
+   *)
+  (try
+     let row = find_child_node_by_content t row Loading in
+     ignore (model#remove row)
+   with Invalid_argument _ | Not_found -> ()
+  );
+
+  (* The original entry has now been loaded, so update its state. *)
+  hdata.state <- IsNode;
+  set_visited t row
index 9207cf7..4bb2c30 100644 (file)
@@ -40,6 +40,8 @@ val clear : t -> unit
 
 val add : t -> string -> Slave.inspection_data -> unit
   (** [add t name data] clears out the widget and adds the operating
-      system and/or filesystems described by the [data] struct.  The
-      [name] parameter should be some host-side (verifiable) name;
-      usually we pass the name of the guest from libvirt here. *)
+      system and/or filesystems described by the [data] struct.
+
+      The [name] parameter should be some host-side (verifiable) name,
+      not any untrusted string from the guest; usually we pass the
+      name of the guest from libvirt here. *)
diff --git a/filetree_markup.ml b/filetree_markup.ml
new file mode 100644 (file)
index 0000000..206700b
--- /dev/null
@@ -0,0 +1,290 @@
+(* Guestfs Browser.
+ * Copyright (C) 2010 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open ExtString
+open ExtList
+open Unix
+
+open CamomileLibrary
+open Default.Camomile
+
+open Utils
+open Filetree_type
+
+open Printf
+
+(* Base colours. XXX Should be configurable somewhere. *)
+let file_color = 0x20, 0x20, 0xff  (* regular file *)
+let dir_color = 0x80, 0x80, 0x20   (* directory *)
+let symlink_color = file_color     (* symlink *)
+let suid_color = 0x20, 0x20, 0x80  (* setuid bit set on regular file *)
+let suid_bgcolor = 0xff, 0xc0, 0xc0
+let sgid_color = suid_color        (* setgid bit set on regular file *)
+let sgid_bgcolor = suid_bgcolor
+let block_color = 0x00, 0x60, 0x60 (* block device *)
+let char_color = block_color       (* char device *)
+let fifo_color = 0x60, 0x00, 0x60  (* fifo *)
+let socket_color = fifo_color      (* socket *)
+let other_color = file_color       (* anything not one of the above *)
+
+(* Mark up a filename for the name_col column.
+ *
+ * See also
+ * http://library.gnome.org/devel/pango/stable/PangoMarkupFormat.html
+ *)
+let rec markup_of_name ?(visited = false) direntry =
+  let name = direntry.Slave.dent_name in
+  let mode = direntry.Slave.dent_stat.Guestfs.mode in
+  if is_directory mode then (           (* directory *)
+    let fg = if not visited then normal dir_color else darken dir_color in
+    sprintf "<span weight=\"bold\" fgcolor=\"%s\">%s</span>"
+      fg (markup_escape name)
+  )
+  else if is_symlink mode then (        (* symlink *)
+    let link = direntry.Slave.dent_link in
+    let fg =
+      if not visited then normal symlink_color else darken symlink_color in
+    sprintf "<span style=\"italic\" fgcolor=\"%s\">%s</span> %s <span style=\"italic\" fgcolor=\"%s\">%s</span>"
+      fg (markup_escape name) utf8_rarrow fg (markup_escape link)
+  )
+  else (                                (* not directory, not symlink *)
+    let fg, bg =
+      if is_regular_file mode then (
+        if is_suid mode then suid_color, Some suid_bgcolor
+        else if is_sgid mode then sgid_color, Some sgid_bgcolor
+        else file_color, None
+      )
+      else if is_block mode then block_color, None
+      else if is_char mode then char_color, None
+      else if is_fifo mode then fifo_color, None
+      else if is_socket mode then socket_color, None
+      else other_color, None in
+    let fg = if not visited then normal fg else darken fg in
+    let bg =
+      match bg with
+      | Some bg -> sprintf " bgcolor=\"%s\"" (normal bg)
+      | None -> "" in
+    sprintf "<span fgcolor=\"%s\"%s>%s</span>"
+      fg bg (markup_escape name)
+  )
+
+(* Mark up a registry key. *)
+and markup_of_regkey ?(visited = false) h node =
+  let name = Hivex.node_name h node in
+  let name = if name = "" then "@" else name in
+  let fg = if not visited then normal dir_color else darken dir_color in
+  sprintf "<span fgcolor=\"%s\">%s</span>" fg (markup_escape name)
+
+(* Mark up a registry value. *)
+and markup_of_regvalue ?(visited = false) h value =
+  debug "markup_of_regvalue";
+  let k = Hivex.value_key h value in
+  let t, v = Hivex.value_value h value in
+
+  (* Ignore long values. *)
+  let len = String.length v in
+  let v =
+    if len >= 256 then
+      sprintf "&lt;%d bytes not printed&gt;" len
+    else (
+      (* Deal as best we can with printing the value. *)
+      match t with
+      | Hivex.REG_NONE -> if v = "" then "" else markup_hex_data v
+      | Hivex.REG_SZ -> markup_windows_string v
+      | Hivex.REG_EXPAND_SZ -> markup_windows_string v
+      | Hivex.REG_BINARY -> markup_hex_data v
+      | Hivex.REG_DWORD ->
+          if len = 4 then
+            sprintf "%08lx" (i32_of_string_le v)
+          else
+            markup_hex_data v
+      | Hivex.REG_DWORD_BIG_ENDIAN ->
+          if len = 4 then
+            sprintf "%08lx" (i32_of_string_be v)
+          else
+            markup_hex_data v
+      | Hivex.REG_LINK -> markup_hex_data v
+      | Hivex.REG_MULTI_SZ -> (* XXX could do better with this *)
+          markup_hex_data v
+      | Hivex.REG_RESOURCE_LIST -> markup_hex_data v
+      | Hivex.REG_FULL_RESOURCE_DESCRIPTOR -> markup_hex_data v
+      | Hivex.REG_RESOURCE_REQUIREMENTS_LIST -> markup_hex_data v
+      | Hivex.REG_QWORD ->
+          if len = 8 then
+            sprintf "%016Lx" (i64_of_string_le v)
+          else
+            markup_hex_data v
+      | Hivex.REG_UNKNOWN i32 -> markup_hex_data v
+    ) in
+
+  let fg = if not visited then normal file_color else darken file_color in
+  sprintf "<span fgcolor=\"%s\">%s</span>=<span fgcolor=\"%s\">%s</span>"
+    fg (markup_escape k) fg v
+
+(* Mark up registry value as hex data. *)
+and markup_hex_data v =
+  let vs = String.explode v in
+  let vs = List.mapi (
+    fun i c ->
+      sprintf "%s%02x" (if i mod 16 = 0 then "\n" else "") (int_of_char c)
+  ) vs in
+  String.concat "," vs
+
+(* Best guess the format of the string and convert to UTF-8. *)
+and markup_windows_string v =
+  let utf16le = CharEncoding.utf16le in
+  let utf8 = CharEncoding.utf8 in
+  try
+    let v = CharEncoding.recode_string ~in_enc:utf16le ~out_enc:utf8 v in
+    (* Registry strings include the final \0 so remove this if present. *)
+    let len = UTF8.length v in
+    let v =
+      if len > 0 && UChar.code (UTF8.get v (len-1)) = 0 then
+        String.sub v 0 (UTF8.last v)
+      else
+        v in
+    markup_escape v
+  with
+  | CharEncoding.Malformed_code
+  | CharEncoding.Out_of_range ->
+      (* Fallback to displaying the string as hex. *)
+      markup_hex_data v
+
+and normal (r, g, b) =
+  let r = if r < 0 then 0 else if r > 255 then 255 else r in
+  let g = if g < 0 then 0 else if g > 255 then 255 else g in
+  let b = if b < 0 then 0 else if b > 255 then 255 else b in
+  sprintf "#%02x%02x%02x" r g b
+
+and darken (r, g, b) =
+  normal (r * 4 / 10, g * 4 / 10, b * 4 / 10)
+
+(* Mark up mode. *)
+let markup_of_mode mode =
+  let c =
+    if is_socket mode then 's'
+    else if is_symlink mode then 'l'
+    else if is_regular_file mode then '-'
+    else if is_block mode then 'b'
+    else if is_directory mode then 'd'
+    else if is_char mode then 'c'
+    else if is_fifo mode then 'p' else '?' in
+  let ru = if is_ru mode then 'r' else '-' in
+  let wu = if is_wu mode then 'w' else '-' in
+  let xu = if is_xu mode then 'x' else '-' in
+  let rg = if is_rg mode then 'r' else '-' in
+  let wg = if is_wg mode then 'w' else '-' in
+  let xg = if is_xg mode then 'x' else '-' in
+  let ro = if is_ro mode then 'r' else '-' in
+  let wo = if is_wo mode then 'w' else '-' in
+  let xo = if is_xo mode then 'x' else '-' in
+  let str = sprintf "%c%c%c%c%c%c%c%c%c%c" c ru wu xu rg wg xg ro wo xo in
+
+  let suid = is_suid mode in
+  let sgid = is_sgid mode in
+  let svtx = is_svtx mode in
+  if suid then str.[3] <- 's';
+  if sgid then str.[6] <- 's';
+  if svtx then str.[9] <- 't';
+
+  "<span color=\"#222222\" size=\"small\">" ^ str ^ "</span>"
+
+(* Mark up dates. *)
+let markup_of_date t =
+  (* Guestfs gives us int64's, we want float which is OCaml's
+   * equivalent of time_t.
+   *)
+  let t = Int64.to_float t in
+
+  let show_full_date () =
+    let tm = localtime t in
+    sprintf "<span color=\"#222222\" size=\"small\">%04d-%02d-%02d %02d:%02d:%02d</span>"
+      (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
+      tm.tm_hour tm.tm_min tm.tm_sec
+  in
+
+  (* How long ago? *)
+  let now = time () in
+  let ago = now -. t in
+  if ago < 0. then (* future *)
+    show_full_date ()
+  else if ago < 60. then
+    "<small>now</small>"
+  else if ago < 60. *. 60. then
+    sprintf "<small>%.0f minutes ago</small>" (ago /. 60.)
+  else if ago < 60. *. 60. *. 24. then
+    sprintf "<small>%.0f hours ago</small>" (ago /. 60. /. 60.)
+  else if ago < 60. *. 60. *. 24. *. 28. then
+    sprintf "<small>%.0f days ago</small>" (ago /. 60. /. 60. /. 24.)
+  else
+    show_full_date ()
+
+(* Mark up file sizes. *)
+let markup_of_size bytes =
+  sprintf "<small>%s</small>" (human_size bytes)
+
+(* Mark up registry value types. *)
+let markup_of_regvaluetype h value =
+  let t, _ = Hivex.value_value h value in
+
+  match t with
+  | Hivex.REG_NONE -> "none(0)"
+  | Hivex.REG_SZ -> "str(1)"
+  | Hivex.REG_EXPAND_SZ -> "str(2)"
+  | Hivex.REG_BINARY -> "hex(3)"
+  | Hivex.REG_DWORD -> "dword(4)"
+  | Hivex.REG_DWORD_BIG_ENDIAN -> "dword(5)"
+  | Hivex.REG_LINK -> "link(6)"
+  | Hivex.REG_MULTI_SZ -> "multi string (7)"
+  | Hivex.REG_RESOURCE_LIST -> "resource list (8)"
+  | Hivex.REG_FULL_RESOURCE_DESCRIPTOR -> "full resource descriptor (9)"
+  | Hivex.REG_RESOURCE_REQUIREMENTS_LIST -> "resource requirements list (10)"
+  | Hivex.REG_QWORD -> "qword (11)"
+  | Hivex.REG_UNKNOWN i32 -> sprintf "type 0x%08lx" i32
+
+(* Mark up registry value sizes. *)
+let markup_of_regvaluesize h value =
+  let _, len = Hivex.value_type h value in
+  sprintf "%d" len
+
+(* This is a bit of a hack.  Ideally just setting 'visited' would
+ * darken the colour when the cell was re-rendered.  However that would
+ * mean we couldn't store other stuff in the name column.  Therefore,
+ * repopulate the name column.
+ *)
+let set_visited ({ model = model; name_col = name_col } as t) row =
+  let hdata = get_hdata t row in
+  if hdata.visited = false then (
+    hdata.visited <- true;
+    match hdata.content with
+    | Directory direntry | File direntry ->
+        debug "set_visited %s" direntry.Slave.dent_name;
+        model#set ~row ~column:name_col
+          (markup_of_name ~visited:true direntry)
+    | RegKey node ->
+        debug "set_visited RegKey";
+        let h = Option.get hdata.hiveh in
+        model#set ~row ~column:name_col
+          (markup_of_regkey ~visited:true h node)
+    | RegValue value ->
+        debug "set_visited RegValue";
+        let h = Option.get hdata.hiveh in
+        model#set ~row ~column:name_col
+          (markup_of_regvalue ~visited:true h value)
+    | Loading | ErrorMessage _ | Info _ | Top _ | TopWinReg _ -> ()
+  )
diff --git a/filetree_markup.mli b/filetree_markup.mli
new file mode 100644 (file)
index 0000000..de4cfb4
--- /dev/null
@@ -0,0 +1,53 @@
+(* Guestfs Browser.
+ * Copyright (C) 2010 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+(** Deals with generating markup and displaying fields in the file tree.
+
+    The types and functions in this file should be considered
+    private to the file tree implementation.
+
+    See {!Filetree} for the full description and public interface. *)
+
+(**/**)
+
+val markup_of_name : ?visited:bool -> Slave.direntry -> string
+  (* Create markup for filenames. *)
+
+val markup_of_date : int64 -> string
+  (* Create markup for dates. *)
+
+val markup_of_size : int64 -> string
+  (* Create markup for sizes. *)
+
+val markup_of_mode : int64 -> string
+  (* Create markup for mode (permissions). *)
+
+val markup_of_regkey : ?visited:bool -> Hivex.t -> Hivex.node -> string
+  (* Create markup for registry keys. *)
+
+val markup_of_regvalue : ?visited:bool -> Hivex.t -> Hivex.value -> string
+  (* Create markup for registry values. *)
+
+val markup_of_regvaluetype : Hivex.t -> Hivex.value -> string
+  (* Create markup for registry value types. *)
+
+val markup_of_regvaluesize : Hivex.t -> Hivex.value -> string
+  (* Create markup for registry value sizes. *)
+
+val set_visited : Filetree_type.t -> Gtk.tree_iter -> unit
+  (* Set a file as visited. *)
index 93ecb62..5946a81 100644 (file)
@@ -19,8 +19,8 @@
 open Printf
 
 open Utils
-
 open Filetree_type
+open Filetree_markup
 
 (* Get the basename of a file, using path conventions which are valid
  * for libguestfs.  So [Filename.basename] won't necessarily work
@@ -160,7 +160,8 @@ let rec disk_usage ({ model = model } as t) path () =
   if not (has_child_node_equals t row content) then (
     (* Create the child node first. *)
     let row = model#insert ~parent:row 0 in
-    store_hdata t row { state=IsLeaf; content=content; visited=false };
+    let hdata = { state=IsLeaf; content=content; visited=false; hiveh=None } in
+    store_hdata t row hdata;
     model#set ~row ~column:t.name_col "<i>Calculating disk usage ...</i>";
 
     Slave.disk_usage src pathname (when_disk_usage t path pathname)
@@ -200,7 +201,9 @@ let display_inspection_data ({ model = model } as t) path () =
       let content = Info "inspection_data" in
       if not (has_child_node_equals t row content) then (
         let row = model#insert ~parent:row 0 in
-        store_hdata t row { state=IsLeaf; content=content; visited=false };
+        let hdata =
+          { state=IsLeaf; content=content; visited=false; hiveh=None } in
+        store_hdata t row hdata;
 
         (* XXX UGHLEE *)
         let data =
index 4b70b76..f39137e 100644 (file)
@@ -17,7 +17,6 @@
  *)
 
 open Utils
-open Printf
 
 (* See struct/field description in .mli file. *)
 type t = {
@@ -35,6 +34,7 @@ and hdata = {
   mutable state : state_t;
   content : content_t;
   mutable visited : bool;
+  mutable hiveh : Hivex.t option;
 }
 
 and state_t =
@@ -48,8 +48,11 @@ and content_t =
   | ErrorMessage of string
   | Info of string
   | Top of Slave.source
+  | TopWinReg of Slave.source * string * string * string
   | Directory of Slave.direntry
   | File of Slave.direntry
+  | RegKey of Hivex.node
+  | RegValue of Hivex.value
 
 (* Store hdata into a row. *)
 let store_hdata {model = model; hash = hash; index_col = index_col} row hdata =
@@ -88,6 +91,8 @@ let find_child_node_by_content ({ model = model } as t) row c =
  *       \_ Directory
  *            \_ Directory
  *                 \_ Loading    <--- you are here
+ *
+ * Note this function cannot be called on registry keys.
  *)
 let rec get_pathname ({ model = model } as t) row =
   let hdata = get_hdata t row in
@@ -106,92 +111,11 @@ let rec get_pathname ({ model = model } as t) row =
         else parent_name ^ "/" ^ name in
       src, path
   | { content=Top src }, _ -> src, "/"
-  | { content=Directory _}, None -> assert false
-  | { content=File _}, None -> assert false
+  | { content=Directory _ }, None -> assert false
+  | { content=File _ }, None -> assert false
   | { content=Loading }, _ -> assert false
-  | { content=ErrorMessage _}, _ -> assert false
-  | { content=Info _}, _ -> assert false
-
-(* Base colours. XXX Should be configurable somewhere. *)
-let file_color = 0x20, 0x20, 0xff  (* regular file *)
-let dir_color = 0x80, 0x80, 0x20   (* directory *)
-let symlink_color = file_color     (* symlink *)
-let suid_color = 0x20, 0x20, 0x80  (* setuid bit set on regular file *)
-let suid_bgcolor = 0xff, 0xc0, 0xc0
-let sgid_color = suid_color        (* setgid bit set on regular file *)
-let sgid_bgcolor = suid_bgcolor
-let block_color = 0x00, 0x60, 0x60 (* block device *)
-let char_color = block_color       (* char device *)
-let fifo_color = 0x60, 0x00, 0x60  (* fifo *)
-let socket_color = fifo_color      (* socket *)
-let other_color = file_color       (* anything not one of the above *)
-
-(* Mark up a filename for the name_col column.
- * 
- * XXX This shouldn't be in Filetree_type module, but we have to have
- * it here because set_visited is here.
- * 
- * See also
- * http://library.gnome.org/devel/pango/stable/PangoMarkupFormat.html
- *)
-let rec markup_of_name ?(visited = false) direntry =
-  let name = direntry.Slave.dent_name in
-  let mode = direntry.Slave.dent_stat.Guestfs.mode in
-  if is_directory mode then (           (* directory *)
-    let fg = if not visited then normal dir_color else darken dir_color in
-    sprintf "<span weight=\"bold\" fgcolor=\"%s\">%s</span>"
-      fg (markup_escape name)
-  )
-  else if is_symlink mode then (        (* symlink *)
-    let link = direntry.Slave.dent_link in
-    let fg =
-      if not visited then normal symlink_color else darken symlink_color in
-    sprintf "<span style=\"italic\" fgcolor=\"%s\">%s</span> %s <span style=\"italic\" fgcolor=\"%s\">%s</span>"
-      fg (markup_escape name) utf8_rarrow fg (markup_escape link)
-  )
-  else (                                (* not directory, not symlink *)
-    let fg, bg =
-      if is_regular_file mode then (
-        if is_suid mode then suid_color, Some suid_bgcolor
-        else if is_sgid mode then sgid_color, Some sgid_bgcolor
-        else file_color, None
-      )
-      else if is_block mode then block_color, None
-      else if is_char mode then char_color, None
-      else if is_fifo mode then fifo_color, None
-      else if is_socket mode then socket_color, None
-      else other_color, None in
-    let fg = if not visited then normal fg else darken fg in
-    let bg =
-      match bg with
-      | Some bg -> sprintf " bgcolor=\"%s\"" (normal bg)
-      | None -> "" in
-    sprintf "<span fgcolor=\"%s\"%s>%s</span>"
-      fg bg (markup_escape name)
-  )
-
-and normal (r, g, b) =
-  let r = if r < 0 then 0 else if r > 255 then 255 else r in
-  let g = if g < 0 then 0 else if g > 255 then 255 else g in
-  let b = if b < 0 then 0 else if b > 255 then 255 else b in
-  sprintf "#%02x%02x%02x" r g b
-
-and darken (r, g, b) =
-  normal (r * 4 / 10, g * 4 / 10, b * 4 / 10)
-
-(* This is a bit of a hack.  Ideally just setting 'visited' would
- * darken the colour when the cell was re-rendered.  However that would
- * mean we couldn't store other stuff in the name column.  Therefore,
- * repopulate the name column.
- *)
-let set_visited ({ model = model; name_col = name_col } as t) row =
-  let hdata = get_hdata t row in
-  if hdata.visited = false then (
-    hdata.visited <- true;
-    match hdata.content with
-    | Directory direntry | File direntry ->
-        debug "set_visited %s" direntry.Slave.dent_name;
-        model#set ~row ~column:name_col
-          (markup_of_name ~visited:true direntry)
-    | Loading | ErrorMessage _ | Info _ | Top _ -> ()
-  )
+  | { content=ErrorMessage _ }, _ -> assert false
+  | { content=Info _ }, _ -> assert false
+  | { content=TopWinReg _ }, _ -> assert false
+  | { content=RegKey _ }, _ -> assert false
+  | { content=RegValue _ }, _ -> assert false
index 590b635..e1bd7da 100644 (file)
@@ -16,7 +16,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *)
 
-(** This is the base class for the file tree.
+(** This is the base module for the file tree.
 
     The types and functions in this file should be considered
     private to the file tree implementation.
@@ -43,6 +43,7 @@ and hdata = {
   mutable state : state_t;
   content : content_t;
   mutable visited : bool;
+  mutable hiveh : Hivex.t option;
 }
 
 (* The type of the hidden column used to implement on-demand loading.
@@ -62,8 +63,12 @@ and content_t =
   | ErrorMessage of string           (* error message node *)
   | Info of string                   (* information node (eg. disk usage) *)
   | Top of Slave.source              (* top level OS or volume node *)
+                                     (* top level Windows Registry node *)
+  | TopWinReg of Slave.source * string * string * string
   | Directory of Slave.direntry      (* a directory *)
   | File of Slave.direntry           (* a file inc. special files *)
+  | RegKey of Hivex.node             (* a registry key (like a dir) *)
+  | RegValue of Hivex.value          (* a registry value (like a file) *)
 
 val store_hdata : t -> Gtk.tree_iter -> hdata -> unit
 val get_hdata : t -> Gtk.tree_iter -> hdata
@@ -79,9 +84,3 @@ val get_pathname : t -> Gtk.tree_iter -> Slave.source * string
   (* Get the full path to a row by chasing up through the tree to the
      top.  This also returns the source (eg. operating system or single
      volume). *)
-
-val markup_of_name : ?visited:bool -> Slave.direntry -> string
-  (* Create markup for filenames. *)
-
-val set_visited : t -> Gtk.tree_iter -> unit
-  (* Set a file as visited. *)
index 57265bc..cb745e3 100644 (file)
@@ -11,15 +11,19 @@ Source0:        http://people.redhat.com/~rjones/guestfs-browser/files/guestfs-b
 
 BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 
+BuildRequires:  hivex-devel >= 1.2.4-3
 BuildRequires:  libguestfs-devel >= 1.7.9
 BuildRequires:  libvirt-devel
 BuildRequires:  ocaml
+BuildRequires:  ocaml-camomile-devel
+BuildRequires:  ocaml-camomile-data
+BuildRequires:  ocaml-extlib-devel
 BuildRequires:  ocaml-findlib-devel
+BuildRequires:  ocaml-hivex-devel
+BuildRequires:  ocaml-lablgtk-devel
 BuildRequires:  ocaml-libvirt-devel
 BuildRequires:  ocaml-libguestfs-devel
 BuildRequires:  ocaml-xml-light-devel
-BuildRequires:  ocaml-extlib-devel
-BuildRequires:  ocaml-lablgtk-devel
 BuildRequires:  /usr/bin/pod2man
 BuildRequires:  /usr/bin/pod2html
 
index 880a2b1..e27ced7 100644 (file)
--- a/slave.ml
+++ b/slave.ml
@@ -65,6 +65,11 @@ and inspection_os = {
   insp_product_name : string;
   insp_type : string;
   insp_windows_systemroot : string option;
+  insp_winreg_DEFAULT : string option;
+  insp_winreg_SAM : string option;
+  insp_winreg_SECURITY : string option;
+  insp_winreg_SOFTWARE : string option;
+  insp_winreg_SYSTEM : string option;
 }
 
 and source = OS of inspection_os | Volume of string
@@ -515,24 +520,72 @@ and open_disk_images images cb =
         [] in
 
   let oses = List.map (
-    fun root -> {
-      insp_root = root;
-      insp_arch = g#inspect_get_arch root;
-      insp_distro = g#inspect_get_distro root;
-      insp_filesystems = g#inspect_get_filesystems root;
-      insp_hostname = g#inspect_get_hostname root;
-      insp_major_version = g#inspect_get_major_version root;
-      insp_minor_version = g#inspect_get_minor_version root;
-      insp_mountpoints = g#inspect_get_mountpoints root;
-      insp_package_format = g#inspect_get_package_format root;
-      insp_package_management = g#inspect_get_package_management root;
-      insp_product_name = g#inspect_get_product_name root;
-      insp_type = g#inspect_get_type root;
-      insp_windows_systemroot =
-        try Some (g#inspect_get_windows_systemroot root)
-        with Guestfs.Error _ -> None
-    }
+    fun root ->
+      let typ = g#inspect_get_type root in
+      let windows_systemroot =
+        if typ <> "windows" then None
+        else (
+          try Some (g#inspect_get_windows_systemroot root)
+          with Guestfs.Error _ -> None
+        ) in
+
+      (* Create most of the OS object that we're going to return.  We
+       * have to pass this to with_mount_ro below which is why we need
+       * to partially create it here.
+       *)
+      let os = {
+        insp_root = root;
+        insp_arch = g#inspect_get_arch root;
+        insp_distro = g#inspect_get_distro root;
+        insp_filesystems = g#inspect_get_filesystems root;
+        insp_hostname = g#inspect_get_hostname root;
+        insp_major_version = g#inspect_get_major_version root;
+        insp_minor_version = g#inspect_get_minor_version root;
+        insp_mountpoints = g#inspect_get_mountpoints root;
+        insp_package_format = g#inspect_get_package_format root;
+        insp_package_management = g#inspect_get_package_management root;
+        insp_product_name = g#inspect_get_product_name root;
+        insp_type = typ;
+        insp_windows_systemroot = windows_systemroot;
+        insp_winreg_DEFAULT = None; (* incomplete, see below *)
+        insp_winreg_SAM = None;
+        insp_winreg_SECURITY = None;
+        insp_winreg_SOFTWARE = None;
+        insp_winreg_SYSTEM = None;
+      } in
+
+      (* We need to mount the root in order to look for Registry hives. *)
+      let winreg_DEFAULT, winreg_SAM, winreg_SECURITY, winreg_SOFTWARE,
+        winreg_SYSTEM =
+        match windows_systemroot with
+        | None -> None, None, None, None, None
+        | Some sysroot ->
+            with_mount_ro g (OS os) (
+              fun () ->
+                let check_for_hive filename =
+                  let path =
+                    sprintf "%s/system32/config/%s" sysroot filename in
+                  try Some (g#case_sensitive_path path)
+                  with Guestfs.Error _ -> None
+                in
+                check_for_hive "default",
+                check_for_hive "sam",
+                check_for_hive "security",
+                check_for_hive "software",
+                check_for_hive "system"
+            ) in
+
+      (* Fill in the remaining struct fields. *)
+      let os = { os with
+                   insp_winreg_DEFAULT = winreg_DEFAULT;
+                   insp_winreg_SAM = winreg_SAM;
+                   insp_winreg_SECURITY = winreg_SECURITY;
+                   insp_winreg_SOFTWARE = winreg_SOFTWARE;
+                   insp_winreg_SYSTEM = winreg_SYSTEM
+               } in
+      os
   ) roots in
+
   let data = {
     insp_all_filesystems = fses;
     insp_oses = oses;
index 2b08e04..406e9db 100644 (file)
--- a/slave.mli
+++ b/slave.mli
@@ -105,6 +105,11 @@ and inspection_os = {
   insp_product_name : string;
   insp_type : string;
   insp_windows_systemroot : string option;
+  insp_winreg_DEFAULT : string option;   (* registry files *)
+  insp_winreg_SAM : string option;
+  insp_winreg_SECURITY : string option;
+  insp_winreg_SOFTWARE : string option;
+  insp_winreg_SYSTEM : string option;
 }
 
 val open_domain : ?fail:exn callback -> string -> inspection_data callback -> unit
index 02bd7a0..ca2432f 100644 (file)
--- a/utils.ml
+++ b/utils.ml
@@ -88,10 +88,14 @@ let unique = let i = ref 0 in fun () -> incr i; !i
 let mklabel text =
   (GMisc.label ~text () :> GObj.widget)
 
-(* XXX No binding for g_markup_escape in lablgtk2. *)
+(* g_markup_escape is not bound by lablgtk2, but we want to provide
+ * extra protection for \0 characters appearing in the string
+ * anyway.
+ *)
 let markup_escape name =
   let f = function
     | '&' -> "&amp;" | '<' -> "&lt;" | '>' -> "&gt;"
+    | '\000' -> "\\0"
     | c -> String.make 1 c
   in
   String.replace_chars f name
@@ -139,3 +143,62 @@ and is_wo mode =           test_bit 0o002L mode
 and is_xo mode =           test_bit 0o001L mode
 
 and test_bit mask mode = Int64.logand mode mask = mask
+
+let tmpdir () =
+  let chan = open_in "/dev/urandom" in
+  let data = String.create 16 in
+  really_input chan data 0 (String.length data);
+  close_in chan;
+  let data = Digest.to_hex (Digest.string data) in
+  (* Note this is secure, because if the name already exists, even as a
+   * symlink, mkdir(2) will fail.
+   *)
+  let tmpdir = Filename.temp_dir_name // sprintf "febootstrap%s.tmp" data in
+  Unix.mkdir tmpdir 0o700;
+  at_exit
+    (fun () ->
+       let cmd = sprintf "rm -rf %s" (Filename.quote tmpdir) in
+       ignore (Sys.command cmd));
+  tmpdir
+
+(* This would be so much simpler with ChriS's delimited
+ * overloading macro XXX
+ *)
+let i32_of_string_le v =
+  let b0 = int_of_char (String.unsafe_get v 0) in
+  let b1 = int_of_char (String.unsafe_get v 1) in
+  let b2 = int_of_char (String.unsafe_get v 2) in
+  let b3 = Int32.of_int (int_of_char (String.unsafe_get v 3)) in
+  Int32.logor
+    (Int32.of_int (b0 lor (b1 lsl 8) lor (b2 lsl 16)))
+    (Int32.shift_left b3 24)
+
+let i32_of_string_be v =
+  let b0 = Int32.of_int (int_of_char (String.unsafe_get v 0)) in
+  let b1 = int_of_char (String.unsafe_get v 1) in
+  let b2 = int_of_char (String.unsafe_get v 2) in
+  let b3 = int_of_char (String.unsafe_get v 3) in
+  Int32.logor
+    (Int32.of_int (b3 lor (b2 lsl 8) lor (b1 lsl 16)))
+    (Int32.shift_left b0 24)
+
+let i64_of_string_le v =
+  let b0 = int_of_char (String.unsafe_get v 0) in
+  let b1 = int_of_char (String.unsafe_get v 1) in
+  let b2 = int_of_char (String.unsafe_get v 2) in
+  let b3 = Int64.of_int (int_of_char (String.unsafe_get v 3)) in
+  let b4 = Int64.of_int (int_of_char (String.unsafe_get v 4)) in
+  let b5 = Int64.of_int (int_of_char (String.unsafe_get v 5)) in
+  let b6 = Int64.of_int (int_of_char (String.unsafe_get v 6)) in
+  let b7 = Int64.of_int (int_of_char (String.unsafe_get v 7)) in
+  Int64.logor
+    (Int64.logor
+       (Int64.logor
+          (Int64.logor
+             (Int64.logor
+                (Int64.of_int (b0 lor (b1 lsl 8) lor (b2 lsl 16)))
+                (Int64.shift_left b3 24))
+             (Int64.shift_left b4 32))
+          (Int64.shift_left b5 40))
+       (Int64.shift_left b6 48))
+    (Int64.shift_left b7 56)
index 1bc1669..ad14dd0 100644 (file)
--- a/utils.mli
+++ b/utils.mli
@@ -75,7 +75,8 @@ val mklabel : string -> GObj.widget
       returned as a generic widget. *)
 
 val markup_escape : string -> string
-  (** Call g_markup_escape. *)
+  (** Like g_markup_escape but with extra protection for strings
+      containing \0 characters. *)
 
 val libguestfs_version_string : unit -> string
   (** Return the version of libguestfs as a string. *)
@@ -107,3 +108,24 @@ val is_ro : int64 -> bool
 val is_wo : int64 -> bool
 val is_xo : int64 -> bool
   (** rwx/ugo bits. *)
+
+val tmpdir : unit -> string
+  (** [tmpdir ()] returns a newly created temporary directory.  The
+      tmp directory is automatically removed when the program exits.
+      Note that a fresh temporary directory is returned each time you
+      call this function. *)
+
+val i32_of_string_le : string -> int32
+  (** [i32_of_string_le str] treats the 4 character string [str] as
+      a little endian 32 bit int.  NB. The string {b must} be
+      4 characters or longer. *)
+
+val i32_of_string_be : string -> int32
+  (** [i32_of_string_le str] treats the 4 character string [str] as
+      a big endian 32 bit int.  NB. The string {b must} be
+      4 characters or longer. *)
+
+val i64_of_string_le : string -> int64
+  (** [i64_of_string_le str] treats the 8 character string [str] as
+      a little endian 64 bit int.  NB. The string {b must} be
+      8 characters or longer. *)