Oops - accidentally left in a call to 'exit'
[virt-mem.git] / extract / fedora-koji / fedora_koji_download_kernels.ml
1 (* Memory info for virtual domains.
2    (C) Copyright 2008 Richard W.M. Jones, Red Hat Inc.
3    http://libvirt.org/
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 *)
19
20 (* This is a script which downloads kernels from Fedora and extracts
21    the kernel layout information.
22
23    The basic plan is as follows:
24
25    (1) Use koji to list out all kernel builds, compare this to
26    what we have already got (in the kernels/ database at the top level
27    of the virt-mem source), and download any kernels we haven't
28    seen already.
29
30    (2) For each kernel, get the kernel-*debuginfo* RPMs (there will
31    be several, one for each architecture, and one for each variant
32    such as PAE).
33
34    (3) For each debuginfo RPM, extract the 'vmlinux' (kernel image)
35    from the RPM.  This contains debugging symbols.
36
37    (4) Run 'pahole -E' (from acme's dwarves library) to extract all
38    the kernel structures.
39
40    (5) Save the kernel name/version/architecture + the output of pahole
41    in the kernels/ directory (the kernels database).
42  *)
43
44 open ExtList
45 open ExtString
46 open Printf
47
48 let (//) = Filename.concat
49
50 (* Wrappers around the XMLRPC calls. *)
51 type build = {
52   package_name : string;                (* eg. "kernel" *)
53   version : string;                     (* eg. "2.6.25" *)
54   release : string;                     (* eg. "1.fc8" *)
55   build_id : int;
56 }
57
58 let string_of_build { package_name = package_name;
59                       version = version; release = release;
60                       build_id = build_id } =
61   sprintf "%d: %s %s %s" build_id package_name version release
62
63 type rpm = {
64   rpm_id : int;                         (* RPM ID (for downloading, etc.) *)
65   rpm_build : build;
66   rpm_name : string;                    (* eg. "kernel" *)
67   rpm_version : string;                 (* eg. "2.6.25" *)
68   rpm_release : string;                 (* eg. "1.fc8" *)
69   rpm_size : int;                       (* size in bytes of the RPM. *)
70   rpm_arch : string;                    (* architecture *)
71 }
72
73 let string_of_rpm { rpm_id = id; rpm_build = { build_id = build_id };
74                     rpm_name = name;
75                     rpm_version = version; rpm_release = release;
76                     rpm_size = size; rpm_arch = arch } =
77   sprintf "%d: (build %d) %s %s %s (%d bytes) %s"
78     id build_id name version release size arch
79
80 let get_string_from_struct name items =
81   match List.assoc name items with
82   | `String str -> str
83   | _ -> invalid_arg (name ^ ": expected string type")
84
85 let get_int_from_struct name items =
86   match List.assoc name items with
87   | `Int i -> i
88   | _ -> invalid_arg (name ^ ": expected int type")
89
90 let koji_list_builds rpc ~prefix =
91   let builds = rpc#call "listBuilds" [
92     `Struct [
93       (* __starstar is some wierd Python thing which is needed for
94        * Python optional arguments to work.
95        *)
96       "__starstar", `Int 1;
97       "prefix", `String prefix;
98     ]
99   ] in
100
101   match builds with
102   | `Array builds ->
103       List.map (
104         function
105         | `Struct items ->
106             (try
107                let package_name = get_string_from_struct "package_name" items in
108                let version = get_string_from_struct "version" items in
109                let release = get_string_from_struct "release" items in
110                let build_id = get_int_from_struct "build_id" items in
111                { package_name = package_name;
112                  version = version; release = release;
113                  build_id = build_id }
114              with
115              | Not_found ->
116                  prerr_endline "missing element in build structure from koji listBuilds() calls";
117                  exit 1
118              | Invalid_argument err ->
119                  prerr_endline err;
120                  exit 1
121             )
122         | t ->
123             prerr_endline "unexpected type from koji listBuilds() call";
124             prerr_endline (XmlRpc.dump t);
125             exit 1
126       ) builds
127   | t ->
128       prerr_endline "unexpected type from koji listBuilds() call:";
129       prerr_endline (XmlRpc.dump t);
130       exit 1
131
132 let koji_list_build_rpms rpc ({ build_id = build_id } as build) =
133   let rpms = rpc#call "listBuildRPMs" [ `Int build_id ] in
134
135   match rpms with
136   | `Array rpms ->
137       List.map (
138         function
139         | `Struct items ->
140             (try
141                let name = get_string_from_struct "name" items in
142                let version = get_string_from_struct "version" items in
143                let release = get_string_from_struct "release" items in
144                let build_id' = get_int_from_struct "build_id" items in
145                let id = get_int_from_struct "id" items in
146                let size = get_int_from_struct "size" items in
147                let arch = get_string_from_struct "arch" items in
148                assert (build_id = build_id');
149                { rpm_name = name; rpm_version = version; rpm_release = release;
150                  rpm_build = build; rpm_id = id; rpm_size = size;
151                  rpm_arch = arch }
152              with
153              | Not_found ->
154                  prerr_endline "missing element in build structure from koji listBuildRPMs() calls";
155                  exit 1
156              | Invalid_argument err ->
157                  prerr_endline err;
158                  exit 1
159             )
160         | t ->
161             prerr_endline "unexpected type from koji listBuildRPMs() call";
162             prerr_endline (XmlRpc.dump t);
163             exit 1
164       ) rpms
165   | t ->
166       prerr_endline "unexpected type from koji listBuildRPMs() call:";
167       prerr_endline (XmlRpc.dump t);
168       exit 1
169
170 (* This gets the RPM download URL for an RPM.  I can't see a way to
171  * get this using the Koji API, but the URLs are fairly predictable
172  * anyway.
173  *)
174 let koji_rpm_download_url { rpm_build = { package_name = build_name };
175                             rpm_name = rpm_name;
176                             rpm_version = version; rpm_release = release;
177                             rpm_arch = arch } =
178   let filename = sprintf "%s-%s-%s.%s.rpm" rpm_name version release arch in
179   let uri = sprintf "http://koji.fedoraproject.org/packages/%s/%s/%s/%s/%s"
180     build_name version release arch filename in
181   uri, filename
182
183 (* Main program. *)
184 let main outputdir =
185   let rpc = new XmlRpc.client "http://koji.fedoraproject.org/kojihub" in
186
187   (* Grab the list of kernel builds from Koji. *)
188   printf "Downloading list of kernel builds from Koji ...\n%!";
189   let builds = koji_list_builds rpc ~prefix:"kernel" in
190
191   (* Only care about "kernel" and "kernel-xen" builds. *)
192   let builds = List.filter (
193     fun { package_name = name } ->
194       name = "kernel" || name = "kernel-xen"
195   ) builds in
196
197   let nr_builds = List.length builds in
198   printf "%d kernel builds found on Koji.\n%!" nr_builds;
199
200   List.iteri (
201     fun i build ->
202       printf "Build %d/%d: %s\n" (i+1) nr_builds (string_of_build build);
203
204       (* List the RPMs in the build. *)
205       let rpms = koji_list_build_rpms rpc build in
206
207       (* Only care about debuginfo builds, and not debuginfo-common. *)
208       let contains_string substr name =
209         try ignore (String.find name substr); true
210         with Invalid_string -> false
211       in
212       let contains_debuginfo = contains_string "debuginfo" in
213       let contains_common = contains_string "common" in
214       let rpms = List.filter (
215         fun { rpm_name = name } ->
216           contains_debuginfo name && not (contains_common name)
217       ) rpms in
218
219       List.iter (
220         fun rpm ->
221           let uri, filename = koji_rpm_download_url rpm in
222           let infofile = outputdir // filename ^ ".info" in
223
224           let infoexists =
225             try ignore (Unix.access infofile [Unix.F_OK]); true
226             with Unix.Unix_error _ -> false in
227
228           if infoexists then
229             printf "Skipping %s\n%!" (string_of_rpm rpm)
230           else (
231             printf "%s\n%!" (string_of_rpm rpm);
232
233             let run cmd =
234               let r = Sys.command cmd in
235               if r <> 0 then
236                 failwith (sprintf "%s: command exited with code %d" cmd r)
237             in
238
239             (* Function to clean up the RPM & the temporary subdirectory
240              * (usr/, used for unpacking the RPM).
241              *)
242             let cleanup () =
243               (try Unix.unlink filename with _ -> ());
244               ignore (Sys.command "rm -rf usr/")
245             in
246
247             cleanup ();
248
249             try
250               Std.finally cleanup (
251                 fun () ->
252                   (* Download the RPM.
253                    * 
254                    * Could use ocurl here (the OCaml CURL library) but
255                    * using CURL as a library is generally more trouble
256                    * than it's worth.  So shell out to 'wget' instead.
257                    *)
258                   printf "Downloading RPM ...\n%!";
259                   run (sprintf "wget --quiet %s" (Filename.quote uri));
260
261                   printf "Finished downloading RPM.\n%!";
262
263                   (* Unpack vmlinux binary from the RPM. *)
264                   run (sprintf "rpm2cpio %s | cpio -id --quiet '*/vmlinux'"
265                          (Filename.quote filename));
266
267                   run (sprintf "find usr/ -name vmlinux -print0 |
268                                 xargs -0 pahole -E > %s.data"
269                          (Filename.quote outputdir // Filename.quote filename));
270
271                   let chan = open_out infofile in
272                   fprintf chan "Source: fedora-koji\n";
273                   fprintf chan "Distribution: Fedora\n";
274                   fprintf chan "RPM_id: %d\n" rpm.rpm_id;
275                   fprintf chan "RPM_build_id: %d\n" rpm.rpm_build.build_id;
276                   fprintf chan "Name: %s\n" rpm.rpm_name;
277                   fprintf chan "Version: %s\n" rpm.rpm_version;
278                   fprintf chan "Release: %s\n" rpm.rpm_release;
279                   fprintf chan "Architecture: %s\n" rpm.rpm_arch;
280                   fprintf chan "RPM_size: %d\n" rpm.rpm_size;
281                   fprintf chan "\n";
282                   close_out chan;
283
284                   run (sprintf "rpm -qip %s >> %s"
285                          (Filename.quote filename) (Filename.quote infofile));
286               ) ()
287             with
288               Failure msg ->
289                 eprintf "%s\n%!" msg (* but continue to next RPM ... *)
290           )
291       ) rpms
292   ) builds
293
294 let () =
295   Random.self_init ();
296
297   (* Create a temporary work directory, chdir into there to run the
298    * main program, then ensure that the temporary directory is cleaned
299    * up when we exit.
300    *)
301   let olddir = Unix.getcwd () in
302   let tmpdir =
303     sprintf "%s/tmp%d%Ld"
304       Filename.temp_dir_name
305       (Unix.getpid ()) (Random.int64 Int64.max_int) in
306
307   Unix.mkdir tmpdir 0o700;
308   Sys.chdir tmpdir;
309
310   let cleanup () =
311     Sys.chdir olddir;
312     ignore (Sys.command (sprintf "rm -rf %s" (Filename.quote tmpdir)))
313   in
314
315   Std.finally cleanup (fun () -> main olddir) ()