Add .gitignore file for git.
[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   (* Sort the builds by build ID in reverse, so that we tend to download
201    * the most recent kernels first.
202    *)
203   let builds =
204     let cmp { build_id = id1 } { build_id = id2 } = compare id2 id1 in
205     List.sort ~cmp builds in
206
207   List.iteri (
208     fun i build ->
209       printf "Build %d/%d: %s\n" (i+1) nr_builds (string_of_build build);
210
211       (* List the RPMs in the build. *)
212       let rpms = koji_list_build_rpms rpc build in
213
214       (* Only care about debuginfo builds, and not debuginfo-common. *)
215       let contains_string substr name =
216         try ignore (String.find name substr); true
217         with Invalid_string -> false
218       in
219       let contains_debuginfo = contains_string "debuginfo" in
220       let contains_common = contains_string "common" in
221       let rpms = List.filter (
222         fun { rpm_name = name } ->
223           contains_debuginfo name && not (contains_common name)
224       ) rpms in
225
226       let nr_rpms = List.length rpms in
227
228       List.iteri (
229         fun j rpm ->
230           let uri, filename = koji_rpm_download_url rpm in
231           let infoname = filename ^ ".info" in
232           let infopath = outputdir // infoname in
233
234           let infoexists =
235             try ignore (Unix.access infopath [Unix.F_OK]); true
236             with Unix.Unix_error _ -> false in
237
238           if infoexists then
239             printf "Skipping %s\n%!" (string_of_rpm rpm)
240           else (
241             printf "%d/%d %d/%d %s\n%!"
242               (i+1) nr_builds (j+1) nr_rpms (string_of_rpm rpm);
243
244             let run cmd =
245               let r = Sys.command cmd in
246               if r <> 0 then
247                 failwith (sprintf "%s: command exited with code %d" cmd r)
248             in
249             let run cmd = ksprintf run cmd in
250
251             (* Function to clean up the RPM & the temporary subdirectory
252              * (usr/, used for unpacking the RPM).
253              *)
254             let cleanup () =
255               (try Unix.unlink filename with _ -> ());
256               ignore (Sys.command "rm -rf *.info *.data usr")
257             in
258
259             cleanup ();
260
261             try
262               Std.finally cleanup (
263                 fun () ->
264                   (* Download the RPM.
265                    * 
266                    * Could use ocurl here (the OCaml CURL library) but
267                    * using CURL as a library is generally more trouble
268                    * than it's worth.  So shell out to 'wget' instead.
269                    *)
270                   printf "Downloading RPM ...\n%!";
271                   run "wget --quiet %s" (Filename.quote uri);
272
273                   printf "Finished downloading RPM.\n%!";
274
275                   (* Unpack vmlinux binary from the RPM. *)
276                   run "rpm2cpio %s | cpio -id --quiet '*/vmlinux'"
277                     (Filename.quote filename);
278
279                   run "find usr/ -name vmlinux -print0 |
280                        xargs -0 pahole -E > %s.data"
281                     (Filename.quote filename);
282
283                   let chan = open_out infoname in
284                   fprintf chan "Source: fedora-koji\n";
285                   fprintf chan "Distribution: Fedora\n";
286                   fprintf chan "RPM_id: %d\n" rpm.rpm_id;
287                   fprintf chan "RPM_build_id: %d\n" rpm.rpm_build.build_id;
288                   fprintf chan "Name: %s\n" rpm.rpm_name;
289                   fprintf chan "Version: %s\n" rpm.rpm_version;
290                   fprintf chan "Release: %s\n" rpm.rpm_release;
291                   fprintf chan "Architecture: %s\n" rpm.rpm_arch;
292                   fprintf chan "RPM_size: %d\n" rpm.rpm_size;
293                   fprintf chan "\n";
294                   close_out chan;
295
296                   run "rpm -qip %s >> %s"
297                     (Filename.quote filename) (Filename.quote infoname);
298
299                   (* Atomically move the info & data files to their final
300                    * destination.
301                    *)
302                   run "mv %s.data %s.data"
303                     (Filename.quote filename)
304                     (Filename.quote (outputdir // filename));
305                   run "mv %s %s"
306                     (Filename.quote infoname) (Filename.quote infopath);
307               ) ()
308             with
309               Failure msg ->
310                 eprintf "%s\n%!" msg (* but continue to next RPM ... *)
311           )
312       ) rpms
313   ) builds
314
315 let () =
316   Random.self_init ();
317
318   (* Create a temporary work directory, chdir into there to run the
319    * main program, then ensure that the temporary directory is cleaned
320    * up when we exit.
321    *)
322   let olddir = Unix.getcwd () in
323   let tmpdir =
324     sprintf "%s/tmp%d%Ld"
325       Filename.temp_dir_name
326       (Unix.getpid ()) (Random.int64 Int64.max_int) in
327
328   Unix.mkdir tmpdir 0o700;
329   Sys.chdir tmpdir;
330
331   let cleanup () =
332     Sys.chdir olddir;
333     ignore (Sys.command (sprintf "rm -rf %s" (Filename.quote tmpdir)))
334   in
335
336   Std.finally cleanup (fun () -> main olddir) ()