helper: Print /modules when verbose >= 2
[febootstrap.git] / src / febootstrap_yum_rpm.ml
1 (* febootstrap 3
2  * Copyright (C) 2009-2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  *)
18
19 (* Yum and RPM support. *)
20
21 open Unix
22 open Printf
23
24 open Febootstrap_package_handlers
25 open Febootstrap_utils
26 open Febootstrap_cmdline
27
28 (* Create a temporary directory for use by all the functions in this file. *)
29 let tmpdir = tmpdir ()
30
31 let yum_rpm_detect () =
32   (file_exists "/etc/redhat-release" || file_exists "/etc/fedora-release") &&
33     Config.yum <> "no" && Config.rpm <> "no"
34
35 let yum_rpm_init () =
36   if use_installed then
37     failwith "yum_rpm driver doesn't support --use-installed"
38
39 let yum_rpm_resolve_dependencies_and_download names =
40   (* Liberate this data from python. *)
41   let tmpfile = tmpdir // "names.tmp" in
42   let py = sprintf "
43 import yum
44 import yum.misc
45 import sys
46
47 verbose = %d
48
49 if verbose:
50     print \"febootstrap_yum_rpm: running python code to query yum and resolve deps\"
51
52 yb = yum.YumBase ()
53 yb.preconf.debuglevel = verbose
54 yb.preconf.errorlevel = verbose
55 if %s:
56     yb.preconf.fn = %S
57 yb.setCacheDir ()
58
59 if verbose:
60     print \"febootstrap_yum_rpm: looking up the base packages from the command line\"
61 deps = dict ()
62 pkgs = yb.pkgSack.returnPackages (patterns=sys.argv[1:])
63 for pkg in pkgs:
64     deps[pkg] = False
65
66 if verbose:
67     print \"febootstrap_yum_rpm: recursively finding all the dependencies\"
68 stable = False
69 while not stable:
70     stable = True
71     for pkg in deps.keys():
72         if deps[pkg] == False:
73             deps[pkg] = []
74             stable = False
75             if verbose:
76                 print (\"febootstrap_yum_rpm: examining deps of %%s\" %%
77                        pkg.name)
78             for r in pkg.requires:
79                 ps = yb.whatProvides (r[0], r[1], r[2])
80                 best = yb._bestPackageFromList (ps.returnPackages ())
81                 if best and best.name != pkg.name:
82                     deps[pkg].append (best)
83                     if not deps.has_key (best):
84                         deps[best] = False
85             deps[pkg] = yum.misc.unique (deps[pkg])
86
87 # Write it to a file because yum spews garbage on stdout.
88 f = open (%S, \"w\")
89 for pkg in deps.keys ():
90     f.write (\"%%s %%s %%s %%s %%s\\n\" %%
91              (pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch))
92 f.close ()
93
94 if verbose:
95     print \"febootstrap_yum_rpm: finished python code\"
96 "
97     (if verbose then 1 else 0)
98     (match yum_config with None -> "False" | Some _ -> "True")
99     (match yum_config with None -> "" | Some filename -> filename)
100     tmpfile in
101   run_python py names;
102   let chan = open_in tmpfile in
103   let lines = input_all_lines chan in
104   close_in chan;
105
106   (* Get fields. *)
107   let pkgs =
108     List.map (
109       fun line ->
110         match string_split " " line with
111         | [name; epoch; version; release; arch] ->
112             name, int_of_string epoch, version, release, arch
113         | _ ->
114             eprintf "febootstrap: bad output from python script: '%s'" line;
115             exit 1
116     ) lines in
117
118   (* Something of a hack for x86_64: exclude all i[3456]86 packages. *)
119   let pkgs =
120     if Config.host_cpu = "x86_64" then (
121       List.filter (
122         function (_, _, _, _, ("i386"|"i486"|"i586"|"i686")) -> false
123         | _ -> true
124       ) pkgs
125     )
126     else pkgs in
127
128   (* Drop the kernel package to save time. *)
129   let pkgs =
130     List.filter (function ("kernel",_,_,_,_) -> false | _ -> true) pkgs in
131
132   (* Exclude packages matching [--exclude] regexps on the command line. *)
133   let pkgs =
134     List.filter (
135       fun (name, _, _, _, _) ->
136         not (List.exists (fun re -> Str.string_match re name 0) excludes)
137     ) pkgs in
138
139   (* Sort the list of packages, and remove duplicates (by name).
140    * XXX This is not quite right: we really want to keep the latest
141    * package if duplicates are found, but that would require a full
142    * version compare function.
143    *)
144   let pkgs = List.sort (fun a b -> compare b a) pkgs in
145   let pkgs =
146     let cmp (name1, _, _, _, _) (name2, _, _, _, _) = compare name1 name2 in
147     uniq ~cmp pkgs in
148   let pkgs = List.sort compare pkgs in
149
150   (* Construct package names. *)
151   let pkgnames = List.map (
152     function
153     | name, 0, version, release, arch ->
154         sprintf "%s-%s-%s.%s" name version release arch
155     | name, epoch, version, release, arch ->
156         sprintf "%d:%s-%s-%s.%s" epoch name version release arch
157   ) pkgs in
158
159   if pkgnames = [] then (
160     eprintf "febootstrap: yum-rpm: error: no packages to download\n";
161     exit 1
162   );
163
164   let cmd = sprintf "yumdownloader%s%s --destdir %s %s"
165     (if verbose then "" else " --quiet")
166     (match yum_config with None -> ""
167      | Some filename -> sprintf " -c %s" filename)
168     (Filename.quote tmpdir)
169     (String.concat " " (List.map Filename.quote pkgnames)) in
170   run_command cmd;
171
172   (* Return list of package filenames. *)
173   List.map (
174     (* yumdownloader doesn't include epoch in the filename *)
175     fun (name, _, version, release, arch) ->
176       sprintf "%s/%s-%s-%s.%s.rpm" tmpdir name version release arch
177   ) pkgs
178
179 let rec yum_rpm_list_files pkg =
180   (* Run rpm -qlp with some extra magic. *)
181   let cmd =
182     sprintf "rpm -q --qf '[%%{FILENAMES} %%{FILEFLAGS:fflags} %%{FILEMODES} %%{FILESIZES}\\n]' -p %s"
183       pkg in
184   let lines = run_command_get_lines cmd in
185
186   let files =
187     filter_map (
188       fun line ->
189         match string_split " " line with
190         | [filename; flags; mode; size] ->
191             let test_flag = String.contains flags in
192             let mode = int_of_string mode in
193             let size = int_of_string size in
194             if test_flag 'd' then None  (* ignore documentation *)
195             else
196               Some (filename, {
197                       ft_dir = mode land 0o40000 <> 0;
198                       ft_ghost = test_flag 'g'; ft_config = test_flag 'c';
199                       ft_mode = mode; ft_size = size;
200                     })
201         | _ ->
202             eprintf "febootstrap: bad output from rpm command: '%s'" line;
203             exit 1
204     ) lines in
205
206   (* I've never understood why the base packages like 'filesystem' don't
207    * contain any /dev nodes at all.  This leaves every program that
208    * bootstraps RPMs to create a varying set of device nodes themselves.
209    * This collection was copied from mock/backend.py.
210    *)
211   let files =
212     let b = Filename.basename pkg in
213     if string_prefix "filesystem-" b then (
214       let dirs = [ "/proc"; "/sys"; "/dev"; "/dev/pts"; "/dev/shm";
215                    "/dev/mapper" ] in
216       let dirs =
217         List.map (fun name ->
218                     name, { ft_dir = true; ft_ghost = false;
219                             ft_config = false; ft_mode = 0o40755;
220                             ft_size = 0 }) dirs in
221       let devs = [ "/dev/null"; "/dev/full"; "/dev/zero"; "/dev/random";
222                    "/dev/urandom"; "/dev/tty"; "/dev/console";
223                    "/dev/ptmx"; "/dev/stdin"; "/dev/stdout"; "/dev/stderr" ] in
224       (* No need to set the mode because these will go into hostfiles. *)
225       let devs =
226         List.map (fun name ->
227                     name, { ft_dir = false; ft_ghost = false;
228                             ft_config = false; ft_mode = 0o644;
229                             ft_size = 0 }) devs in
230       dirs @ devs @ files
231     ) else files in
232
233   files
234
235 let yum_rpm_get_file_from_package pkg file =
236   debug "extracting %s from %s ..." file (Filename.basename pkg);
237
238   let outfile = tmpdir // file in
239   let cmd =
240     sprintf "umask 0000; rpm2cpio %s | (cd %s && cpio --quiet -id .%s)"
241       (Filename.quote pkg) (Filename.quote tmpdir) (Filename.quote file) in
242   run_command cmd;
243   outfile
244
245 let () =
246   let ph = {
247     ph_detect = yum_rpm_detect;
248     ph_init = yum_rpm_init;
249     ph_resolve_dependencies_and_download =
250       yum_rpm_resolve_dependencies_and_download;
251     ph_list_files = yum_rpm_list_files;
252     ph_get_file_from_package = yum_rpm_get_file_from_package;
253   } in
254   register_package_handler "yum-rpm" ph