--- /dev/null
+(* Memory info 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.
+*)
+
+(* This is a script which downloads kernels from Fedora and extracts
+ the kernel layout information.
+
+ The basic plan is as follows:
+
+ (1) Use koji to list out all kernel builds, compare this to
+ what we have already got (in the kernels/ database at the top level
+ of the virt-mem source), and download any kernels we haven't
+ seen already.
+
+ (2) For each kernel, get the kernel-*debuginfo* RPMs (there will
+ be several, one for each architecture, and one for each variant
+ such as PAE).
+
+ (3) For each debuginfo RPM, extract the 'vmlinux' (kernel image)
+ from the RPM. This contains debugging symbols.
+
+ (4) Run 'pahole -E' (from acme's dwarves library) to extract all
+ the kernel structures.
+
+ (5) Save the kernel name/version/architecture + the output of pahole
+ in the kernels/ directory (the kernels database).
+ *)
+
+open ExtList
+open ExtString
+open Printf
+
+let (//) = Filename.concat
+
+(* Wrappers around the XMLRPC calls. *)
+type build = {
+ package_name : string; (* eg. "kernel" *)
+ version : string; (* eg. "2.6.25" *)
+ release : string; (* eg. "1.fc8" *)
+ build_id : int;
+}
+
+let string_of_build { package_name = package_name;
+ version = version; release = release;
+ build_id = build_id } =
+ sprintf "%d: %s %s %s" build_id package_name version release
+
+type rpm = {
+ rpm_id : int; (* RPM ID (for downloading, etc.) *)
+ rpm_build : build;
+ rpm_name : string; (* eg. "kernel" *)
+ rpm_version : string; (* eg. "2.6.25" *)
+ rpm_release : string; (* eg. "1.fc8" *)
+ rpm_size : int; (* size in bytes of the RPM. *)
+ rpm_arch : string; (* architecture *)
+}
+
+let string_of_rpm { rpm_id = id; rpm_build = { build_id = build_id };
+ rpm_name = name;
+ rpm_version = version; rpm_release = release;
+ rpm_size = size; rpm_arch = arch } =
+ sprintf "%d: (build %d) %s %s %s (%d bytes) %s"
+ id build_id name version release size arch
+
+let get_string_from_struct name items =
+ match List.assoc name items with
+ | `String str -> str
+ | _ -> invalid_arg (name ^ ": expected string type")
+
+let get_int_from_struct name items =
+ match List.assoc name items with
+ | `Int i -> i
+ | _ -> invalid_arg (name ^ ": expected int type")
+
+let koji_list_builds rpc ~prefix =
+ let builds = rpc#call "listBuilds" [
+ `Struct [
+ (* __starstar is some wierd Python thing which is needed for
+ * Python optional arguments to work.
+ *)
+ "__starstar", `Int 1;
+ "prefix", `String prefix;
+ ]
+ ] in
+
+ match builds with
+ | `Array builds ->
+ List.map (
+ function
+ | `Struct items ->
+ (try
+ let package_name = get_string_from_struct "package_name" items in
+ let version = get_string_from_struct "version" items in
+ let release = get_string_from_struct "release" items in
+ let build_id = get_int_from_struct "build_id" items in
+ { package_name = package_name;
+ version = version; release = release;
+ build_id = build_id }
+ with
+ | Not_found ->
+ prerr_endline "missing element in build structure from koji listBuilds() calls";
+ exit 1
+ | Invalid_argument err ->
+ prerr_endline err;
+ exit 1
+ )
+ | t ->
+ prerr_endline "unexpected type from koji listBuilds() call";
+ prerr_endline (XmlRpc.dump t);
+ exit 1
+ ) builds
+ | t ->
+ prerr_endline "unexpected type from koji listBuilds() call:";
+ prerr_endline (XmlRpc.dump t);
+ exit 1
+
+let koji_list_build_rpms rpc ({ build_id = build_id } as build) =
+ let rpms = rpc#call "listBuildRPMs" [ `Int build_id ] in
+
+ match rpms with
+ | `Array rpms ->
+ List.map (
+ function
+ | `Struct items ->
+ (try
+ let name = get_string_from_struct "name" items in
+ let version = get_string_from_struct "version" items in
+ let release = get_string_from_struct "release" items in
+ let build_id' = get_int_from_struct "build_id" items in
+ let id = get_int_from_struct "id" items in
+ let size = get_int_from_struct "size" items in
+ let arch = get_string_from_struct "arch" items in
+ assert (build_id = build_id');
+ { rpm_name = name; rpm_version = version; rpm_release = release;
+ rpm_build = build; rpm_id = id; rpm_size = size;
+ rpm_arch = arch }
+ with
+ | Not_found ->
+ prerr_endline "missing element in build structure from koji listBuildRPMs() calls";
+ exit 1
+ | Invalid_argument err ->
+ prerr_endline err;
+ exit 1
+ )
+ | t ->
+ prerr_endline "unexpected type from koji listBuildRPMs() call";
+ prerr_endline (XmlRpc.dump t);
+ exit 1
+ ) rpms
+ | t ->
+ prerr_endline "unexpected type from koji listBuildRPMs() call:";
+ prerr_endline (XmlRpc.dump t);
+ exit 1
+
+(* This gets the RPM download URL for an RPM. I can't see a way to
+ * get this using the Koji API, but the URLs are fairly predictable
+ * anyway.
+ *)
+let koji_rpm_download_url { rpm_build = { package_name = build_name };
+ rpm_name = rpm_name;
+ rpm_version = version; rpm_release = release;
+ rpm_arch = arch } =
+ let filename = sprintf "%s-%s-%s.%s.rpm" rpm_name version release arch in
+ let uri = sprintf "http://koji.fedoraproject.org/packages/%s/%s/%s/%s/%s"
+ build_name version release arch filename in
+ uri, filename
+
+(* Main program. *)
+let main outputdir =
+ let rpc = new XmlRpc.client "http://koji.fedoraproject.org/kojihub" in
+
+ (* Grab the list of kernel builds from Koji. *)
+ printf "Downloading list of kernel builds from Koji ...\n%!";
+ let builds = koji_list_builds rpc ~prefix:"kernel" in
+
+ (* Only care about "kernel" and "kernel-xen" builds. *)
+ let builds = List.filter (
+ fun { package_name = name } ->
+ name = "kernel" || name = "kernel-xen"
+ ) builds in
+
+ let nr_builds = List.length builds in
+ printf "%d kernel builds found on Koji.\n%!" nr_builds;
+
+ List.iteri (
+ fun i build ->
+ printf "Build %d/%d: %s\n" (i+1) nr_builds (string_of_build build);
+
+ (* List the RPMs in the build. *)
+ let rpms = koji_list_build_rpms rpc build in
+
+ (* Only care about debuginfo builds, and not debuginfo-common. *)
+ let contains_string substr name =
+ try ignore (String.find name substr); true
+ with Invalid_string -> false
+ in
+ let contains_debuginfo = contains_string "debuginfo" in
+ let contains_common = contains_string "common" in
+ let rpms = List.filter (
+ fun { rpm_name = name } ->
+ contains_debuginfo name && not (contains_common name)
+ ) rpms in
+
+ List.iter (
+ fun rpm ->
+ let uri, filename = koji_rpm_download_url rpm in
+ let infofile = outputdir // filename ^ ".info" in
+
+ let infoexists =
+ try ignore (Unix.access infofile [Unix.F_OK]); true
+ with Unix.Unix_error _ -> false in
+
+ if infoexists then
+ printf "Skipping %s\n%!" (string_of_rpm rpm)
+ else (
+ printf "%s\n%!" (string_of_rpm rpm);
+
+ let run cmd =
+ let r = Sys.command cmd in
+ if r <> 0 then
+ failwith (sprintf "%s: command exited with code %d" cmd r)
+ in
+
+ (* Function to clean up the RPM & the temporary subdirectory
+ * (usr/, used for unpacking the RPM).
+ *)
+ let cleanup () =
+ (try Unix.unlink filename with _ -> ());
+ ignore (Sys.command "rm -rf usr/")
+ in
+
+ cleanup ();
+
+ try
+ Std.finally cleanup (
+ fun () ->
+ (* Download the RPM.
+ *
+ * Could use ocurl here (the OCaml CURL library) but
+ * using CURL as a library is generally more trouble
+ * than it's worth. So shell out to 'wget' instead.
+ *)
+ printf "Downloading RPM ...\n%!";
+ run (sprintf "wget --quiet %s" (Filename.quote uri));
+
+ printf "Finished downloading RPM.\n%!";
+
+ (* Unpack vmlinux binary from the RPM. *)
+ run (sprintf "rpm2cpio %s | cpio -id --quiet '*/vmlinux'"
+ (Filename.quote filename));
+
+ run (sprintf "find usr/ -name vmlinux -print0 |
+ xargs -0 pahole -E > %s.data"
+ (Filename.quote outputdir // Filename.quote filename));
+
+ let chan = open_out infofile in
+ fprintf chan "Source: fedora-koji\n";
+ fprintf chan "Distribution: Fedora\n";
+ fprintf chan "RPM_id: %d\n" rpm.rpm_id;
+ fprintf chan "RPM_build_id: %d\n" rpm.rpm_build.build_id;
+ fprintf chan "Name: %s\n" rpm.rpm_name;
+ fprintf chan "Version: %s\n" rpm.rpm_version;
+ fprintf chan "Release: %s\n" rpm.rpm_release;
+ fprintf chan "Architecture: %s\n" rpm.rpm_arch;
+ fprintf chan "RPM_size: %d\n" rpm.rpm_size;
+ fprintf chan "\n";
+ close_out chan;
+
+ run (sprintf "rpm -qip %s >> %s"
+ (Filename.quote filename) (Filename.quote infofile));
+ ) ()
+ with
+ Failure msg ->
+ eprintf "%s\n%!" msg (* but continue to next RPM ... *)
+ )
+ ) rpms;
+
+ exit 2
+ ) builds
+
+let () =
+ Random.self_init ();
+
+ (* Create a temporary work directory, chdir into there to run the
+ * main program, then ensure that the temporary directory is cleaned
+ * up when we exit.
+ *)
+ let olddir = Unix.getcwd () in
+ let tmpdir =
+ sprintf "%s/tmp%d%Ld"
+ Filename.temp_dir_name
+ (Unix.getpid ()) (Random.int64 Int64.max_int) in
+
+ Unix.mkdir tmpdir 0o700;
+ Sys.chdir tmpdir;
+
+ let cleanup () =
+ Sys.chdir olddir;
+ ignore (Sys.command (sprintf "rm -rf %s" (Filename.quote tmpdir)))
+ in
+
+ Std.finally cleanup (fun () -> main olddir) ()