summaryrefslogtreecommitdiffstats
path: root/src/febootstrap_yum_rpm.ml
blob: ca74f4a27f24afd48d241bf78a4b5001da26ff70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
(* febootstrap 3
 * Copyright (C) 2009-2010 Red Hat Inc.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *)

(* Yum and RPM support. *)

open Unix
open Printf

open Febootstrap_package_handlers
open Febootstrap_utils
open Febootstrap_cmdline

(* Create a temporary directory for use by all the functions in this file. *)
let tmpdir = tmpdir ()

let yum_rpm_detect () =
  (file_exists "/etc/redhat-release" || file_exists "/etc/fedora-release") &&
    Config.yum <> "no" && Config.rpm <> "no"

let yum_rpm_resolve_dependencies_and_download names =
  (* Liberate this data from python. *)
  let tmpfile = tmpdir // "names.tmp" in
  let py = sprintf "
import yum
import yum.misc
import sys

yb = yum.YumBase ()
yb.preconf.debuglevel = %d
yb.preconf.errorlevel = %d
if %s:
    yb.preconf.fn = %S
yb.setCacheDir ()

# Look up the base packages from the command line.
deps = dict ()
pkgs = yb.pkgSack.returnPackages (patterns=sys.argv[1:])
for pkg in pkgs:
    deps[pkg] = False

# Recursively find all the dependencies.
stable = False
while not stable:
    stable = True
    for pkg in deps.keys():
        if deps[pkg] == False:
            deps[pkg] = []
            stable = False
            for r in pkg.requires:
                ps = yb.whatProvides (r[0], r[1], r[2])
                best = yb._bestPackageFromList (ps.returnPackages ())
                if best.name != pkg.name:
                    deps[pkg].append (best)
                    if not deps.has_key (best):
                        deps[best] = False
            deps[pkg] = yum.misc.unique (deps[pkg])

# Write it to a file because yum spews garbage on stdout.
f = open (%S, \"w\")
for pkg in deps.keys ():
    f.write (\"%%s %%s %%s %%s %%s\\n\" %%
             (pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch))
f.close ()
"
    (if verbose then 1 else 0)
    (if verbose then 1 else 0)
    (match yum_config with None -> "False" | Some _ -> "True")
    (match yum_config with None -> "" | Some filename -> filename)
    tmpfile in
  run_python py names;
  let chan = open_in tmpfile in
  let lines = input_all_lines chan in
  close_in chan;

  (* Get fields. *)
  let pkgs =
    List.map (
      fun line ->
        match string_split " " line with
        | [name; epoch; version; release; arch] ->
            name, int_of_string epoch, version, release, arch
        | _ ->
            eprintf "febootstrap: bad output from python script: '%s'" line;
            exit 1
    ) lines in

  (* Something of a hack for x86_64: exclude all i[3456]86 packages. *)
  let pkgs =
    if Config.host_cpu = "x86_64" then (
      List.filter (
        function (_, _, _, _, ("i386"|"i486"|"i586"|"i686")) -> false
        | _ -> true
      ) pkgs
    )
    else pkgs in

  (* Drop the kernel package to save time. *)
  let pkgs =
    List.filter (function ("kernel",_,_,_,_) -> false | _ -> true) pkgs in

  (* Exclude packages matching [--exclude] regexps on the command line. *)
  let pkgs =
    List.filter (
      fun (name, _, _, _, _) ->
        not (List.exists (fun re -> Str.string_match re name 0) excludes)
    ) pkgs in

  (* Sort the list of packages, and remove duplicates (by name).
   * XXX This is not quite right: we really want to keep the latest
   * package if duplicates are found, but that would require a full
   * version compare function.
   *)
  let pkgs = List.sort (fun a b -> compare b a) pkgs in
  let pkgs =
    let cmp (name1, _, _, _, _) (name2, _, _, _, _) = compare name1 name2 in
    uniq ~cmp pkgs in
  let pkgs = List.sort compare pkgs in

  (* Construct package names. *)
  let pkgnames = List.map (
    function
    | name, 0, version, release, arch ->
        sprintf "%s-%s-%s.%s" name version release arch
    | name, epoch, version, release, arch ->
        sprintf "%d:%s-%s-%s.%s" epoch name version release arch
  ) pkgs in

  if pkgnames = [] then (
    eprintf "febootstrap: yum-rpm: error: no packages to download\n";
    exit 1
  );

  let cmd = sprintf "yumdownloader%s%s --destdir %s %s"
    (if verbose then "" else " --quiet")
    (match yum_config with None -> ""
     | Some filename -> sprintf " -c %s" filename)
    (Filename.quote tmpdir)
    (String.concat " " (List.map Filename.quote pkgnames)) in
  run_command cmd;

  (* Return list of package filenames. *)
  List.map (
    (* yumdownloader doesn't include epoch in the filename *)
    fun (name, _, version, release, arch) ->
      sprintf "%s/%s-%s-%s.%s.rpm" tmpdir name version release arch
  ) pkgs

let rec yum_rpm_list_files pkg =
  (* Run rpm -qlp with some extra magic. *)
  let cmd =
    sprintf "rpm -q --qf '[%%{FILENAMES} %%{FILEFLAGS:fflags} %%{FILEMODES} %%{FILESIZES}\\n]' -p %s"
      pkg in
  let lines = run_command_get_lines cmd in

  let files =
    filter_map (
      fun line ->
        match string_split " " line with
        | [filename; flags; mode; size] ->
            let test_flag = String.contains flags in
            let mode = int_of_string mode in
	    let size = int_of_string size in
            if test_flag 'd' then None  (* ignore documentation *)
            else
              Some (filename, {
                      ft_dir = mode land 0o40000 <> 0;
                      ft_ghost = test_flag 'g'; ft_config = test_flag 'c';
                      ft_mode = mode; ft_size = size;
                    })
        | _ ->
            eprintf "febootstrap: bad output from rpm command: '%s'" line;
            exit 1
    ) lines in

  (* I've never understood why the base packages like 'filesystem' don't
   * contain any /dev nodes at all.  This leaves every program that
   * bootstraps RPMs to create a varying set of device nodes themselves.
   * This collection was copied from mock/backend.py.
   *)
  let files =
    let b = Filename.basename pkg in
    if string_prefix "filesystem-" b then (
      let dirs = [ "/proc"; "/sys"; "/dev"; "/dev/pts"; "/dev/shm";
                   "/dev/mapper" ] in
      let dirs =
        List.map (fun name ->
                    name, { ft_dir = true; ft_ghost = false;
                            ft_config = false; ft_mode = 0o40755;
			    ft_size = 0 }) dirs in
      let devs = [ "/dev/null"; "/dev/full"; "/dev/zero"; "/dev/random";
                   "/dev/urandom"; "/dev/tty"; "/dev/console";
                   "/dev/ptmx"; "/dev/stdin"; "/dev/stdout"; "/dev/stderr" ] in
      (* No need to set the mode because these will go into hostfiles. *)
      let devs =
        List.map (fun name ->
                    name, { ft_dir = false; ft_ghost = false;
                            ft_config = false; ft_mode = 0o644;
			    ft_size = 0 }) devs in
      dirs @ devs @ files
    ) else files in

  files

let yum_rpm_get_file_from_package pkg file =
  debug "extracting %s from %s ..." file (Filename.basename pkg);

  let outfile = tmpdir // file in
  let cmd =
    sprintf "umask 0000; rpm2cpio %s | (cd %s && cpio --quiet -id .%s)"
      (Filename.quote pkg) (Filename.quote tmpdir) (Filename.quote file) in
  run_command cmd;
  outfile

let () =
  let ph = {
    ph_detect = yum_rpm_detect;
    ph_resolve_dependencies_and_download =
      yum_rpm_resolve_dependencies_and_download;
    ph_list_files = yum_rpm_list_files;
    ph_get_file_from_package = yum_rpm_get_file_from_package;
  } in
  register_package_handler "yum-rpm" ph