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