Preparation for capture subcommand:
authorRichard W.M. Jones <rjones@redhat.com>
Tue, 15 Jul 2008 16:47:30 +0000 (17:47 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Tue, 15 Jul 2008 16:47:30 +0000 (17:47 +0100)
 - extra hooks at various stages
 - allow tools to provide extra command line options
 - updated MANIFEST

MANIFEST
dmesg/virt_dmesg.ml
lib/.depend
lib/Makefile.in
lib/virt_mem.ml
lib/virt_mem.mli
lib/virt_mem_capture.ml [new file with mode: 0644]
mem/.depend
mem/virt_mem_main.ml
ps/virt_ps.ml
uname/virt_uname.ml

index 976b11f..916849a 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -9,6 +9,7 @@ HACKING
 install-sh
 lib/.depend
 lib/Makefile.in
+lib/virt_mem_capture.ml
 lib/virt_mem.ml
 lib/virt_mem.mli
 lib/virt_mem_mmap.ml
index 0931e91..dd41d26 100644 (file)
@@ -25,7 +25,7 @@ open Virt_mem_mmap
 
 let run debug images =
   List.iter (
-    fun (name, arch, mem, lookup_ksym) ->
+    fun (_, name, arch, mem, lookup_ksym) ->
       try
        (* I don't know why but this symbol doesn't exist in 2.6.9
         * even in kallsyms.  Hence this won't work with that kernel.
@@ -80,4 +80,4 @@ virt-dmesg prints the kernel messages for virtual machines running
 under libvirt.  The output is similar to the ordinary dmesg command
 run inside the virtual machine."
 
-let () = Virt_mem.register "dmesg" summary description true run
+let () = Virt_mem.register "dmesg" summary description ~run
index 33e3139..552adb7 100644 (file)
@@ -1,5 +1,7 @@
 virt_mem.cmi: virt_mem_utils.cmo virt_mem_mmap.cmi 
 virt_mem_mmap.cmi: virt_mem_utils.cmo 
+virt_mem_capture.cmo: virt_mem_gettext.cmo virt_mem.cmi 
+virt_mem_capture.cmx: virt_mem_gettext.cmx virt_mem.cmx 
 virt_mem.cmo: virt_mem_version.cmo virt_mem_utils.cmo virt_mem_mmap.cmi \
     virt_mem_gettext.cmo virt_mem.cmi 
 virt_mem.cmx: virt_mem_version.cmx virt_mem_utils.cmx virt_mem_mmap.cmx \
index 0a757e8..373e452 100644 (file)
@@ -52,7 +52,8 @@ OBJS          = virt_mem_gettext.cmo \
                  virt_mem_utils.cmo \
                  virt_mem_mmap_c.o \
                  virt_mem_mmap.cmo \
-                 virt_mem.cmo
+                 virt_mem.cmo \
+                 virt_mem_capture.cmo
 XOBJS          = $(OBJS:%.cmo=%.cmx)
 
 all:   $(TARGETS)
index 1f21a66..61d5968 100644 (file)
@@ -43,7 +43,14 @@ let max_memory_peek = 65536 (* XXX Use D.max_peek function *)
 type ksym = string
 
 type image =
-    string
+    int option
+    * string
+    * Virt_mem_utils.architecture
+    * ([`Wordsize], [`Endian]) Virt_mem_mmap.t
+
+type image_with_ksyms =
+    int option
+    * string
     * Virt_mem_utils.architecture
     * ([`Wordsize], [`Endian]) Virt_mem_mmap.t
     * (ksym -> MMap.addr)
@@ -55,23 +62,16 @@ type kallsyms_compr =
 (* When tools register themselves, they are added to this list.
  * Later, we will alphabetize the list.
  *)
-let tools = ref [
-  "capture", (
-    "capture",
-    s_"capture memory image for post-mortem analysis",
-    s_"Capture a memory image to a file for later post-mortem
-analysis.  Use the '-o memoryimage' option to specify the
-output file.
-
-Other tools can load the memory image using the '-t' option.",
-    false,
-    (fun _ _ -> ())
-  );
-]
+let tools = ref []
 
 (* Registration function used by the tools. *)
-let register name summary description is_cmd run_fn =
-  tools := (name, (name, summary, description, is_cmd, run_fn)) :: !tools
+let register ?(external_cmd = true) ?(extra_args = [])
+    ?argcheck ?beforeksyms ?run
+    name summary description =
+  tools :=
+    (name, (name, summary, description, external_cmd, extra_args,
+           argcheck, beforeksyms, run))
+  :: !tools
 
 (* Main program, called from mem/virt_mem_main.ml when all the
  * tools have had a chance to register themselves.
@@ -90,7 +90,7 @@ let main () =
    * module to properly parse the command line (below), so that
    * we can have a usage message ready.
    *)
-  let tool =
+  let tool, ignore_first_anon_arg =
     let prog = Sys.executable_name in  (* eg. "/usr/bin/virt-dmesg.opt" *)
     let prog = Filename.basename prog in(* eg. "virt-dmesg.opt" *)
     let prog =                         (* eg. "virt-dmesg" *)
@@ -99,7 +99,7 @@ let main () =
       if String.starts_with prog "virt-" then
        String.sub prog 5 (String.length prog - 5)
       else prog in
-    try Some (List.assoc prog tools)
+    try Some (List.assoc prog tools), false
     with Not_found ->
       let arg1 =                       (* First non-option argument. *)
        match Array.to_list Sys.argv with
@@ -112,7 +112,7 @@ let main () =
            in
            loop args in
       match arg1 with
-      | None -> None
+      | None -> None, false
       | Some prog ->                   (* Recognisable first argument? *)
          let prog =
            try Filename.chop_extension prog with Invalid_argument _ -> prog in
@@ -120,16 +120,17 @@ let main () =
            if String.starts_with prog "virt-" then
              String.sub prog 5 (String.length prog - 5)
            else prog in
-         (try Some (List.assoc prog tools) with Not_found -> None) in
+         (try Some (List.assoc prog tools), true
+          with Not_found -> None, false) in
 
   (* Make a usage message. *)
   let usage_msg =
     match tool with
     | None ->                          (* Generic usage message. *)
        let tools = List.map (
-         fun (name, (_, summary, _, is_cmd, _)) ->
-           if is_cmd then "virt-"^name, summary
-           else           "virt-mem "^name, summary
+         fun (name, (_, summary, _, external_cmd, _, _, _, _)) ->
+           if external_cmd then "virt-"^name, summary
+           else                 "virt-mem "^name, summary
        ) tools in
        (* Maximum width of field in the left hand column. *)
        let max_width =
@@ -154,8 +155,9 @@ To display extra help for a single tool, do:
 Options:") tools
 
                                         (* Tool-specific usage message. *)
-    | Some (name, summary, description, is_cmd, _) ->
-       let cmd = if is_cmd then "virt-" ^ name else "virt-mem " ^ name in
+    | Some (name, summary, description, external_cmd, _, _, _, _) ->
+       let cmd =
+         if external_cmd then "virt-" ^ name else "virt-mem " ^ name in
 
        sprintf (f_"\
 
@@ -167,9 +169,10 @@ Description:
 Options:") cmd summary description in
 
   (* Now begin proper parsing of the command line arguments. *)
-
-  (* Debug messages. *)
   let debug = ref false in
+  let images = ref [] in
+  let uri = ref "" in
+  let anon_args = ref [] in
 
   (* Default wordsize. *)
   let def_wordsize = ref None in
@@ -211,17 +214,14 @@ Options:") cmd summary description in
     | str -> def_text_addr := Int64.of_string str
   in
 
-  (* List of kernel images. *)
-  let images = ref [] in
-  let uri = ref "" in
-  let anon_args = ref [] in
-
+  (* Handle -t option. *)
   let memory_image filename =
     images :=
       (!def_wordsize, !def_endian, !def_architecture, !def_text_addr, filename)
     :: !images
   in
 
+  (* Handle --version option. *)
   let version () =
     printf "virt-mem %s\n" Virt_mem_version.version;
 
@@ -232,41 +232,67 @@ Options:") cmd summary description in
     exit 0
   in
 
-  let argspec = Arg.align [
-    "-A", Arg.String set_architecture,
-      "arch " ^ s_"Set kernel architecture, endianness and word size";
-    "-E", Arg.String set_endian,
-      "endian " ^ s_"Set kernel endianness";
-    "-T", Arg.String set_text_addr,
-      "addr " ^ s_"Set kernel text address";
-    "-W", Arg.String set_wordsize,
-      "addr " ^ s_"Set kernel word size";
-    "-c", Arg.Set_string uri,
-      "uri " ^ s_ "Connect to URI";
-    "--connect", Arg.Set_string uri,
-      "uri " ^ s_ "Connect to URI";
-    "--debug", Arg.Set debug,
-      " " ^ s_"Debug mode (default: false)";
-    "-t", Arg.String memory_image,
-      "image " ^ s_"Use saved kernel memory image";
-    "--version", Arg.Unit version,
-      " " ^ s_"Display version and exit";
-  ] in
-
+  (* Function to collect up any anonymous args (domain names/IDs). *)
   let anon_arg str = anon_args := str :: !anon_args in
+
+  (* Construct the argspec.
+   * May include extra arguments specified by the tool.
+   *)
+  let argspec =
+    let extra_args = match tool with
+      | None -> []
+      | Some (_, _, _, _, extra_args, _, _, _) -> extra_args in
+    let argspec = [
+      "-A", Arg.String set_architecture,
+        "arch " ^ s_"Set kernel architecture, endianness and word size";
+      "-E", Arg.String set_endian,
+        "endian " ^ s_"Set kernel endianness";
+      "-T", Arg.String set_text_addr,
+        "addr " ^ s_"Set kernel text address";
+      "-W", Arg.String set_wordsize,
+        "addr " ^ s_"Set kernel word size";
+      "-c", Arg.Set_string uri,
+        "uri " ^ s_ "Connect to URI";
+      "--connect", Arg.Set_string uri,
+        "uri " ^ s_ "Connect to URI";
+      "--debug", Arg.Set debug,
+        " " ^ s_"Debug mode (default: false)";
+      "-t", Arg.String memory_image,
+        "image " ^ s_"Use saved kernel memory image";
+      "--version", Arg.Unit version,
+        " " ^ s_"Display version and exit";
+    ] @ extra_args in
+
+    (* Sort options alphabetically on first alpha character. *)
+    let cmp (a,_,_) (b,_,_) =
+      let chars = "-" in
+      let a = String.strip ~chars a and b = String.strip ~chars b in
+      compare a b
+    in
+    let argspec = List.sort ~cmp argspec in
+    (* Make the options line up nicely. *)
+    Arg.align argspec in
+
+  (* Parse the command line.  This will exit if --version or --help found. *)
   Arg.parse argspec anon_arg usage_msg;
 
   let images = !images in
   let debug = !debug in
   let uri = if !uri = "" then None else Some !uri in
+
+  (* Discard the first anonymous argument if, above, we previously
+   * found it contained the tool name.
+   *)
   let anon_args = List.rev !anon_args in
+  let anon_args =
+    if ignore_first_anon_arg then List.tl anon_args else anon_args in
 
   (* At this point, either --help was specified on the command line
    * (and so the program has exited) or we must have determined tool,
    * or the user didn't give us a valid tool (eg. "virt-mem foobar").
    * Detect that final case now and give an error.
    *)
-  let name, _, _, _, run_fn =
+  let name, _, _, _, _, argcheck, beforeksyms, run =
     match tool with
     | Some t -> t
     | None ->
@@ -277,6 +303,12 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
   in
   if debug then eprintf "tool = %s\n%!" name;
 
+  (* Optional argument checking in the tool. *)
+  (match argcheck with
+   | None -> ()
+   | Some argcheck -> argcheck debug
+  );
+
   (* Get the kernel images. *)
   let images =
     if images = [] then (
@@ -340,6 +372,7 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
 
       List.map (
        fun (dom, _) ->
+         let id = D.get_id dom in
          let name = D.get_name dom in
 
          let wordsize =
@@ -398,7 +431,7 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
          let mem = MMap.set_wordsize mem wordsize in
          let mem = MMap.set_endian mem endian in
 
-         (name, arch, mem)
+         ((Some id, name, arch, mem) : image)
       ) xmls
     ) else (
       (* One or more -t options passed. *)
@@ -446,13 +479,27 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
          let mem = MMap.set_wordsize mem wordsize in
          let mem = MMap.set_endian mem endian in
 
-         (filename, arch, mem)
+         ((None, filename, arch, mem) : image)
       ) images
     ) in
 
+  (* Optional callback into the tool before we start looking for
+   * kernel symbols.
+   *)
+  (match beforeksyms with
+   | None -> ()
+   | Some beforeksyms -> beforeksyms debug images
+  );
+
+  (* If there is no run function, then there is no point continuing
+   * with the rest of the program (kernel symbol analysis) ...
+   *)
+  if run = None then exit 0;
+
+  (* Now kernel symbol analysis starts ... *)
   let images =
     List.map (
-      fun (name, arch, mem) ->
+      fun (domid, name, arch, mem) ->
        (* Look for some common entries in the exported symbol table and
         * from that find the symbol table itself.  These are just
         * supposed to be symbols which are very likely to be present
@@ -857,8 +904,12 @@ Use 'virt-mem --help' for more help or read the manual page virt-mem(1)");
            lookup_ksym
        in
 
-       ((name, arch, mem, lookup_ksym) : image)
+       ((domid, name, arch, mem, lookup_ksym) : image_with_ksyms)
     ) images in
 
-  (* Run the actual tool. *)
-  run_fn debug images
+  (* Run the tool's main function. *)
+  (match run with
+   | None -> ()
+   | Some run ->
+       run debug images
+  )
index 1dc1c23..58b879e 100644 (file)
@@ -21,15 +21,23 @@ type ksym = string
   (** A kernel symbol name. *)
 
 type image =
-    string
+    int option
+    * string
+    * Virt_mem_utils.architecture
+    * ([`Wordsize], [`Endian]) Virt_mem_mmap.t
+  (** A memory image from a domain. *)
+
+type image_with_ksyms =
+    int option
+    * string
     * Virt_mem_utils.architecture
     * ([`Wordsize], [`Endian]) Virt_mem_mmap.t
     * (ksym -> Virt_mem_mmap.addr)
-  (** An image after it has been processed by the code common to
-      all commands.
+  (** An image after it has been processed to find kernel symbols.
 
       The tuple fields are:
-      - name
+      - domain ID (if known)
+      - name, usually the domain name
       - architecture (eg. I386)
       - kernel memory map (wordsize & endianness already determined)
       - a function to look up kernel symbols.  It raises [Not_found]
@@ -37,15 +45,35 @@ type image =
         table could not be found at all.
   *)
 
-val register : string -> string -> Arg.usage_msg -> bool -> (bool -> image list -> unit) -> unit
+val register :
+  ?external_cmd:bool ->
+  ?extra_args:(Arg.key * Arg.spec * Arg.doc) list ->
+  ?argcheck:(bool -> unit) ->
+  ?beforeksyms:(bool -> image list -> unit) ->
+  ?run:(bool -> image_with_ksyms list -> unit) ->
+  string -> string -> Arg.usage_msg ->
+  unit
   (** Tools register themselves with this call.
 
-      The parameters are:
+      The anonymous parameters are:
       - tool name (eg. "uname")
       - short summary
       - full usage message
-      - is it a virt-cmd?
-      - run function (invoked as [run verbose images])
+
+      The optional callback functions are:
+      - [?argcheck] called after arguments have been fully parsed
+      so that the program can do any additional checks needed (eg.
+      on [extra_args]),
+      - [?beforeksyms] called after images are loaded and before
+      kernel symbols are analyzed,
+      - [?run] called after kernel symbols have been analyzed
+      (almost all tools supply this callback function).
+
+      Pass [~external_cmd:false] if this tool doesn't have an
+      external 'virt-tool' link.
+
+      Pass [~extra_args:...] if this tool needs extra command
+      line options.
   *)
 
 val main : unit -> unit
diff --git a/lib/virt_mem_capture.ml b/lib/virt_mem_capture.ml
new file mode 100644 (file)
index 0000000..413af56
--- /dev/null
@@ -0,0 +1,90 @@
+(* Memory info command for virtual domains.
+   (C) Copyright 2008 Richard W.M. Jones, Red Hat Inc.
+   http://libvirt.org/
+
+   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Implements 'virt-mem capture' command.
+ *)
+
+open Printf
+open ExtString
+
+open Virt_mem_gettext.Gettext
+
+(* This will contain what is passed by the user as '-o' option. *)
+let output_filename = ref ""
+
+(* Early argument check. *)
+let argcheck debug =
+  (* -o flag must have been specified. *)
+  let output_filename = !output_filename in
+  if output_filename = "" then (
+    prerr_endline (s_"virt-mem capture: '-o memoryimage' option is required");
+    exit 1
+  )
+
+(* Capture the images before kernel symbol analysis is attempted.
+ * Just save them to the output file(s).
+ *)
+let rec beforeksyms debug = function
+  | [] ->
+      prerr_endline
+       (s_"virt-mem capture: warning: no kernel images were captured")
+  | [image] ->
+      (* Single image is saved to output_filename. *)
+      save_image image !output_filename
+  | images ->
+      (* Multiple images are saved to output_filename.ID where ID
+       * is the domain ID (if known) or a mangled domain name.
+       *)
+      List.iter (
+       fun ((domid, domname, _, _) as image) ->
+         let filename =
+           !output_filename ^ "." ^
+           match domid with
+           | Some id -> string_of_int id
+           | None ->
+               let f = function
+                 | ('a'..'z'|'A'..'Z'|'0'..'9'|'_' as c) -> String.make 1 c
+                 | _ -> ""
+               in
+               String.replace_chars f domname in
+         save_image image filename
+      ) images
+
+and save_image (_, domname, arch, mmap) filename =
+  printf (f_"virt-mem capture: saving kernel image from %s to filename %s\n")
+    domname filename;
+
+  assert false
+
+let summary = s_"capture memory image for post-mortem analysis"
+let description = s_"Capture a memory image to a file for later post-mortem
+analysis.  Use the '-o memoryimage' option to specify the
+output file.
+
+Other tools can load the memory image using the '-t' option."
+
+let extra_args = [
+  "-o", Arg.Set_string output_filename,
+    "memoryimage " ^s_"Set output filename"
+]
+
+let () =
+  Virt_mem.register
+    ~external_cmd:false ~extra_args
+    ~argcheck ~beforeksyms
+    "capture" summary description
index 2ad2fc5..fd7322a 100644 (file)
@@ -1,2 +1,2 @@
-virt_mem_main.cmo: ../lib/virt_mem.cmi 
-virt_mem_main.cmx: ../lib/virt_mem.cmx 
+virt_mem_main.cmo: ../lib/virt_mem_capture.cmo ../lib/virt_mem.cmi 
+virt_mem_main.cmx: ../lib/virt_mem_capture.cmx ../lib/virt_mem.cmx 
index 32d89ff..78c7ab7 100644 (file)
    Links everything into a single executable and invokes 'main'.
  *)
 
-Virt_mem.main ()
+(* Because internal commands like 'virt-mem capture' are stored
+ * in a library [*.cma/*.cmxa], they don't automatically get
+ * loaded/registered unless someone seems to be using them.  So
+ * we need to pretend to be using them here.  External commands
+ * like 'virt-ps' are OK because they are linked as *.cmo/*.cmx
+ * files.
+ *)
+let _ = Virt_mem_capture.summary
+
+(* Call main program. *)
+let () = Virt_mem.main ()
index 4c995d8..065b4c3 100644 (file)
@@ -30,4 +30,4 @@ let description = s_"\
 virt-ps prints a process listing for virtual machines running under
 libvirt."
 
-let () = Virt_mem.register "ps" summary description true run
+let () = Virt_mem.register "ps" summary description ~run
index f2f6ddc..e90b5a6 100644 (file)
@@ -57,7 +57,7 @@ let run debug images =
   in
 
   List.iter (
-    fun (name, arch, mem, lookup_ksym) ->
+    fun (_, name, arch, mem, lookup_ksym) ->
       (* In Linux 2.6.25, the symbol is init_uts_ns.
        * http://lxr.linux.no/linux/init/version.c
        *)
@@ -94,4 +94,4 @@ virt-uname prints the uname information such as OS version,
 architecture and node name for virtual machines running under
 libvirt."
 
-let () = Virt_mem.register "uname" summary description true run
+let () = Virt_mem.register "uname" summary description ~run