2 * Copyright (C) 2009-2010 Red Hat Inc.
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.
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.
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
22 open Febootstrap_package_handlers
23 open Febootstrap_utils
24 open Febootstrap_cmdline
26 (* Create a temporary directory for use by all the functions in this file. *)
27 let tmpdir = tmpdir ()
30 debug "%s %s" Config.package_name Config.package_version;
32 (* Instead of printing out warnings as we go along, accumulate them
33 * in lists and print them all out at the end.
35 let warn_unreadable = ref [] in
37 (* Determine which package manager this system uses. *)
39 let ph = get_package_handler () in
41 debug "selected package handler: %s" (get_package_handler_name ());
43 (* Not --names: check files exist. *)
44 if not names_mode then (
47 if not (file_exists pkg) then (
48 eprintf "febootstrap: %s: no such file (did you miss out the --names option?)\n" pkg;
54 (* --names: resolve the package list to a full list of package names
55 * (including dependencies).
59 let packages = ph.ph_resolve_dependencies_and_download packages in
60 debug "resolved packages: %s" (String.concat " " packages);
65 (* Get the list of files. *)
70 let files = ph.ph_list_files pkg in
71 List.map (fun (filename, ft) -> filename, ft, pkg) files
75 (* Sort and combine duplicate files. *)
77 let files = List.sort compare files in
79 let combine (name1, ft1, pkg1) (name2, ft2, pkg2) =
80 (* Rules for combining files. *)
81 if ft1.ft_config || ft2.ft_config then (
82 (* It's a fairly frequent bug in Fedora for two packages to
83 * incorrectly list the same config file. Allow this, provided
84 * the size of both files is 0.
86 if ft1.ft_size = 0 && ft2.ft_size = 0 then
89 eprintf "febootstrap: error: %s is a config file which is listed in two packages (%s, %s)\n"
94 else if (ft1.ft_dir || ft2.ft_dir) && (not (ft1.ft_dir && ft2.ft_dir)) then (
95 eprintf "febootstrap: error: %s appears as both directory and ordinary file (%s, %s)\n"
99 else if ft1.ft_ghost then
105 let rec loop = function
107 | (name1, _, _ as f1) :: (name2, _, _ as f2) :: fs when name1 = name2 ->
108 let f = combine f1 f2 in loop (f :: fs)
109 | f :: fs -> f :: loop fs
113 (* Because we may have excluded some packages, and also because of
114 * distribution packaging errors, it's not necessarily true that a
115 * directory is created before each file in that directory.
116 * Determine those missing directories and add them now.
119 let insert_dir, dir_seen =
120 let h = Hashtbl.create (List.length files) in
121 let insert_dir dir = Hashtbl.replace h dir true in
122 let dir_seen dir = Hashtbl.mem h dir in
127 fun (path, { ft_dir = is_dir }, _ as f) ->
132 let parent = Filename.dirname path in
133 if dir_seen parent then []
136 let newdir = (parent, { ft_dir = true; ft_config = false;
137 ft_ghost = false; ft_mode = 0o40755;
140 newdir :: loop parent
143 List.rev (f :: loop path)
145 List.flatten files in
148 debug "%d files and directories" (List.length files);
151 fun (name, { ft_dir = dir; ft_ghost = ghost; ft_config = config;
152 ft_mode = mode; ft_size = size }, pkg) ->
153 printf "%s [%s%s%s%o %d] from %s\n" name
154 (if dir then "dir " else "")
155 (if ghost then "ghost " else "")
156 (if config then "config " else "")
162 (* Split the list of files into ones for hostfiles and ones for base image. *)
163 let p_hmac = Str.regexp "^\\..*\\.hmac$" in
165 let hostfiles = ref []
166 and baseimgfiles = ref [] in
168 fun (path, {ft_dir = dir; ft_ghost = ghost; ft_config = config} ,_ as f) ->
169 let file = Filename.basename path in
171 (* Ignore boot files, kernel, kernel modules. Supermin appliances
172 * are booted from external kernel and initrd, and
173 * febootstrap-supermin-helper copies the host kernel modules.
174 * Note we want to keep the /boot and /lib/modules directory entries.
176 if string_prefix "/boot/" path then ()
177 else if string_prefix "/lib/modules/" path then ()
179 (* Always write directory names to both output files. *)
181 hostfiles := f :: !hostfiles;
182 baseimgfiles := f :: !baseimgfiles;
185 (* Timezone configuration is config, but copy it from host system. *)
186 else if path = "/etc/localtime" then
187 hostfiles := f :: !hostfiles
189 (* Ignore FIPS files (.*.hmac) (RHBZ#654638). *)
190 else if Str.string_match p_hmac file 0 then ()
192 (* Ghost files are created empty in the base image. *)
194 baseimgfiles := f :: !baseimgfiles
196 (* For config files we can't rely on the host-installed copy
197 * since the admin may have modified then. We have to get the
198 * original file from the package and put it in the base image.
201 baseimgfiles := f :: !baseimgfiles
203 (* Anything else comes from the host. *)
205 hostfiles := f :: !hostfiles
207 let hostfiles = List.rev !hostfiles
208 and baseimgfiles = List.rev !baseimgfiles in
210 (* Write hostfiles. *)
212 (* Regexps used below. *)
213 let p_ld_so = Str.regexp "^ld-[.0-9]+\\.so$" in
214 let p_libbfd = Str.regexp "^libbfd-.*\\.so$" in
215 let p_libgcc = Str.regexp "^libgcc_s-.*\\.so\\.\\([0-9]+\\)$" in
216 let p_libntfs3g = Str.regexp "^libntfs-3g\\.so\\..*$" in
217 let p_lib123so = Str.regexp "^lib\\(.*\\)-[-.0-9]+\\.so$" in
219 Str.regexp "^lib\\(.*\\)-[-.0-9]+\\.so\\.\\([0-9]+\\)\\." in
220 let p_libso123 = Str.regexp "^lib\\(.*\\)\\.so\\.\\([0-9]+\\)\\." in
221 let ntfs3g_once = ref false in
223 let chan = open_out (tmpdir // "hostfiles") in
225 fun (path, {ft_dir = is_dir; ft_ghost = ghost; ft_config = config;
226 ft_mode = mode }, _) ->
227 let dir = Filename.dirname path in
228 let file = Filename.basename path in
231 fprintf chan "%s\n" path
233 (* Warn about hostfiles which are unreadable by non-root. We
234 * won't be able to add those to the appliance at run time, but
235 * there's not much else we can do about it except get the
236 * distros to fix this nonsense.
238 else if mode land 0o004 = 0 then
239 warn_unreadable := path :: !warn_unreadable
241 (* Replace fixed numbers in some library names by wildcards. *)
242 else if Str.string_match p_ld_so file 0 then
243 fprintf chan "%s/ld-*.so\n" dir
245 (* Special case for libbfd. *)
246 else if Str.string_match p_libbfd file 0 then
247 fprintf chan "%s/libbfd-*.so\n" dir
249 (* Special case for libgcc_s-<gccversion>-<date>.so.N *)
250 else if Str.string_match p_libgcc file 0 then
251 fprintf chan "%s/libgcc_s-*.so.%s\n" dir (Str.matched_group 1 file)
253 (* Special case for libntfs-3g.so.* *)
254 else if Str.string_match p_libntfs3g file 0 then (
255 if not !ntfs3g_once then (
256 fprintf chan "%s/libntfs-3g.so.*\n" dir;
261 (* libfoo-1.2.3.so *)
262 else if Str.string_match p_lib123so file 0 then
263 fprintf chan "%s/lib%s-*.so\n" dir (Str.matched_group 1 file)
265 (* libfoo-1.2.3.so.123 (but NOT '*.so.N') *)
266 else if Str.string_match p_lib123so123 file 0 then
267 fprintf chan "%s/lib%s-*.so.%s.*\n" dir
268 (Str.matched_group 1 file) (Str.matched_group 2 file)
270 (* libfoo.so.1.2.3 (but NOT '*.so.N') *)
271 else if Str.string_match p_libso123 file 0 then
272 fprintf chan "%s/lib%s.so.%s.*\n" dir
273 (Str.matched_group 1 file) (Str.matched_group 2 file)
275 (* Anything else comes from the host. *)
277 fprintf chan "%s\n" path
283 * We have to create directories and copy files to tmpdir/root
284 * and then call out to cpio to construct the initrd.
286 let rootdir = tmpdir // "root" in
289 fun (path, { ft_dir = is_dir; ft_ghost = ghost; ft_config = config;
290 ft_mode = mode }, pkg) ->
291 (* Always write directory names to both output files. *)
293 (* Directory permissions are fixed up below. *)
294 if path <> "/" then mkdir (rootdir // path) 0o755
297 (* Ghost files are just touched with the correct perms. *)
299 let chan = open_out (rootdir // path) in
301 chmod (rootdir // path) (mode land 0o777 lor 0o400)
304 (* For config files we can't rely on the host-installed copy
305 * since the admin may have modified it. We have to get the
306 * original file from the package.
308 else if config then (
309 let outfile = ph.ph_get_file_from_package pkg path in
311 (* Note that the output config file might not be a regular file. *)
312 let statbuf = lstat outfile in
314 let destfile = rootdir // path in
316 (* Depending on the file type, copy it to destination. *)
317 match statbuf.st_kind with
319 (* Unreadable files (eg. /etc/gshadow). Make readable. *)
320 if statbuf.st_perm = 0 then chmod outfile 0o400;
323 (Filename.quote outfile) (Filename.quote destfile) in
325 chmod destfile (mode land 0o777 lor 0o400)
327 let link = readlink outfile in
328 symlink link destfile
329 | S_DIR -> assert false
334 eprintf "febootstrap: error: %s: don't know how to handle this type of file\n" path;
339 assert false (* should not be reached *)
342 (* Fix up directory permissions, in reverse order. Since we don't
343 * want to have a read-only directory that we can't write into above.
346 fun (path, { ft_dir = is_dir; ft_mode = mode }, _) ->
347 if is_dir then chmod (rootdir // path) (mode land 0o777 lor 0o700)
348 ) (List.rev baseimgfiles);
350 (* Construct the 'base.img' initramfs. Feed in the list of filenames
351 * partly because we conveniently have them, and partly because
352 * this results in a nice alphabetical ordering in the cpio file.
354 (*let cmd = sprintf "ls -lR %s" rootdir in
355 ignore (Sys.command cmd);*)
357 sprintf "(cd %s && cpio --quiet -o -0 -H newc) > %s"
358 rootdir (tmpdir // "base.img") in
359 let chan = open_process_out cmd in
360 List.iter (fun (path, _, _) -> fprintf chan ".%s\000" path) baseimgfiles;
361 let stat = close_process_out chan in
365 eprintf "febootstrap: command '%s' failed (returned %d), see earlier error messages\n" cmd i;
368 eprintf "febootstrap: command '%s' killed by signal %d" cmd i;
371 eprintf "febootstrap: command '%s' stopped by signal %d" cmd i;
375 (* Undo directory permissions, because rm -rf can't delete files in
376 * unreadable directories.
379 fun (path, { ft_dir = is_dir; ft_mode = mode }, _) ->
380 if is_dir then chmod (rootdir // path) 0o755
381 ) (List.rev baseimgfiles);
383 (* Print warnings. *)
385 (match !warn_unreadable with
388 eprintf "febootstrap: warning: some host files are unreadable by non-root\n";
389 eprintf "febootstrap: warning: get your distro to fix these files:\n";
391 (fun path -> eprintf "\t%s\n%!" path)
392 (List.sort compare paths)
396 (* Near-atomically copy files to the final output directory. *)
397 debug "writing %s ..." (outputdir // "base.img");
400 (Filename.quote (tmpdir // "base.img"))
401 (Filename.quote (outputdir // "base.img")) in
403 debug "writing %s ..." (outputdir // "hostfiles");
406 (Filename.quote (tmpdir // "hostfiles"))
407 (Filename.quote (outputdir // "hostfiles")) in