febootstrap: Use contents of installed Debian packages instead of downloading and...
[febootstrap.git] / src / febootstrap_debian.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 (* Debian 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 installed_pkgs =
32   run_command_get_lines "dpkg-query --show --showformat='${Package}\\n'"
33
34 let debian_detect () =
35   file_exists "/etc/debian_version" &&
36     Config.aptitude <> "no" && Config.apt_cache <> "no" && Config.dpkg <> "no"
37
38 let rec debian_resolve_dependencies_and_download names =
39   let cmd =
40     sprintf "%s depends --recurse -i %s | grep -v '^[<[:space:]]'"
41       Config.apt_cache
42       (String.concat " " (List.map Filename.quote names)) in
43   let pkgs = run_command_get_lines cmd in
44   let pkgs =
45     if Config.apt_cache_depends_recurse_broken then
46       workaround_broken_apt_cache_depends_recurse (sort_uniq pkgs)
47     else
48       pkgs in
49
50   (* Exclude packages matching [--exclude] regexps on the command line. *)
51   let pkgs =
52     List.filter (
53       fun name ->
54         not (List.exists (fun re -> Str.string_match re name 0) excludes)
55     ) pkgs in
56
57   let present_pkgs, download_pkgs = List.partition (
58     fun pkg -> List.exists ((=) pkg) installed_pkgs
59   ) pkgs in
60
61   debug "wanted packages (present / download): %s / %s\n"
62     (String.concat " " present_pkgs)
63     (String.concat " " download_pkgs);
64
65   (* Download the packages. *)
66   if (List.length download_pkgs > 0)
67   then (
68     let cmd =
69       sprintf "umask 0000; cd %s && %s download %s"
70         (Filename.quote tmpdir)
71         Config.aptitude
72         (String.concat " " (List.map Filename.quote download_pkgs)) in
73     run_command cmd
74   );
75
76   (* Find out what aptitude downloaded. *)
77   let files = Sys.readdir tmpdir in
78
79   let download_pkgs = List.map (
80     fun pkg ->
81       (* Look for 'pkg_*.deb' in the list of files. *)
82       let pre = pkg ^ "_" in
83       let r = ref "" in
84       try
85         for i = 0 to Array.length files - 1 do
86           if string_prefix pre files.(i) then (
87             r := files.(i);
88             files.(i) <- "";
89             raise Exit
90           )
91         done;
92         eprintf "febootstrap: aptitude: error: no file was downloaded corresponding to package %s\n" pkg;
93         exit 1
94       with
95           Exit -> !r
96   ) download_pkgs in
97
98   List.sort compare (List.append present_pkgs download_pkgs)
99
100 (* On Ubuntu 10.04 LTS, apt-cache depends --recurse is broken.  It
101  * doesn't return the full list of dependencies.  Therefore recurse
102  * into these dependencies one by one until we reach a fixpoint.
103  *)
104 and workaround_broken_apt_cache_depends_recurse names =
105   debug "workaround for broken 'apt-cache depends --recurse' command:\n  %s"
106     (String.concat " " names);
107
108   let names' =
109     List.map (
110       fun name ->
111         let cmd =
112           sprintf "%s depends --recurse -i %s | grep -v '^[<[:space:]]'"
113             Config.apt_cache (Filename.quote name) in
114         run_command_get_lines cmd
115     ) names in
116   let names' = List.flatten names' in
117   let names' = sort_uniq names' in
118   if names <> names' then
119     workaround_broken_apt_cache_depends_recurse names'
120   else
121     names
122
123 let debian_list_files_downloaded pkg =
124   debug "unpacking %s ..." pkg;
125
126   (* We actually need to extract the file in order to get the
127    * information about modes etc.
128    *)
129   let pkgdir = tmpdir // pkg ^ ".d" in
130   mkdir pkgdir 0o755;
131   let cmd =
132     sprintf "umask 0000; dpkg-deb --fsys-tarfile %s | (cd %s && tar xf -)"
133       (tmpdir // pkg) pkgdir in
134   run_command cmd;
135
136   let cmd = sprintf "cd %s && find ." pkgdir in
137   let lines = run_command_get_lines cmd in
138
139   let files = List.map (
140     fun path ->
141       assert (path.[0] = '.');
142       (* No leading '.' *)
143       let path =
144         if path = "." then "/"
145         else String.sub path 1 (String.length path - 1) in
146
147       (* Find out what it is and get the canonical filename. *)
148       let statbuf = lstat (pkgdir // path) in
149       let is_dir = statbuf.st_kind = S_DIR in
150
151       (* No per-file metadata like in RPM, but we can synthesize it
152        * from the path.
153        *)
154       let config = statbuf.st_kind = S_REG && string_prefix "/etc/" path in
155
156       let mode = statbuf.st_perm in
157
158       (path, { ft_dir = is_dir; ft_config = config; ft_mode = mode;
159                ft_ghost = false; ft_size = statbuf.st_size })
160   ) lines in
161
162   files
163
164 let debian_list_files_installed pkg =
165   debug "using installed package %s ..." pkg;
166   let cmd = sprintf "dpkg-query --listfiles %s" pkg in
167   let lines = run_command_get_lines cmd in
168   (* filter out lines not directly describing fs objects such as
169      "package diverts others to: /path/to/..." *)
170   let lines = List.filter (
171     fun l -> l.[0] = '/' && l.[1] != '.'
172   ) lines in
173   let files = List.map (
174     fun path ->
175       let statbuf = lstat path in
176       let is_dir = statbuf.st_kind = S_DIR in
177       let config = statbuf.st_kind = S_REG && string_prefix "/etc/" path in
178       let mode = statbuf.st_perm in
179       (path, { ft_dir = is_dir; ft_config = config; ft_mode = mode;
180                ft_ghost = false; ft_size = statbuf.st_size })
181   ) lines in
182   files
183
184 let debian_list_files ?(use_installed=false) pkg =
185   if use_installed && List.exists ((=) pkg) installed_pkgs then
186     debian_list_files_installed pkg
187   else
188     debian_list_files_downloaded pkg
189
190 (* Easy because we already unpacked the archive above. *)
191 let debian_get_file_from_package ?(use_installed=false) pkg file =
192   if use_installed && List.exists (fun p -> p = pkg) installed_pkgs then
193     file
194   else
195     tmpdir // pkg ^ ".d" // file
196
197 let () =
198   let ph = {
199     ph_detect = debian_detect;
200     ph_resolve_dependencies_and_download =
201       debian_resolve_dependencies_and_download;
202     ph_list_files = debian_list_files;
203     ph_get_file_from_package = debian_get_file_from_package;
204   } in
205   register_package_handler "debian" ph