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