From d52fcf7521b8f9b1ec7bbeaaf93840af37d363cf Mon Sep 17 00:00:00 2001
From: Richard Jones <rjones@redhat.com>
Date: Mon, 11 May 2009 14:51:22 +0100
Subject: [PATCH] Be more careful about removing files in fakeroot during
 minimization. Added febootstrap-install program.

---
 .gitignore                  |  1 +
 Makefile.am                 | 20 ++++++++++++++++++
 febootstrap-install.pod     | 49 +++++++++++++++++++++++++++++++++++++++++++++
 febootstrap-install.sh      | 45 +++++++++++++++++++++++++++++++++++++++++
 febootstrap-minimize.sh     | 42 +++++++++++++++++++++-----------------
 febootstrap-run.pod         | 16 ++++-----------
 febootstrap-to-initramfs.sh |  2 ++
 febootstrap.pod             | 19 +++++++++++++-----
 8 files changed, 159 insertions(+), 35 deletions(-)
 create mode 100644 febootstrap-install.pod
 create mode 100755 febootstrap-install.sh

diff --git a/.gitignore b/.gitignore
index db68bd9..24fc34d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ config.status
 configure
 febootstrap
 febootstrap-run
+febootstrap-install
 febootstrap-minimize
 febootstrap-to-initramfs
 install-sh
diff --git a/Makefile.am b/Makefile.am
index c23498b..b239bbf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,6 +22,7 @@ SUBDIRS = examples
 bin_SCRIPTS = \
 	febootstrap \
 	febootstrap-run \
+	febootstrap-install \
 	febootstrap-minimize \
 	febootstrap-to-initramfs
 DISTCLEANFILES = $(bin_SCRIPTS)
@@ -38,6 +39,12 @@ febootstrap-run: febootstrap-run.sh
 	chmod 0555 $@-t
 	mv $@-t $@
 
+febootstrap-install: febootstrap-install.sh
+	rm -f $@
+	cp $< $@-t
+	chmod 0555 $@-t
+	mv $@-t $@
+
 febootstrap-minimize: febootstrap-minimize.sh
 	rm -f $@
 	cp $< $@-t
@@ -53,6 +60,7 @@ febootstrap-to-initramfs: febootstrap-to-initramfs.sh
 man_MANS = \
 	febootstrap.8 \
 	febootstrap-run.8 \
+	febootstrap-install.8 \
 	febootstrap-minimize.8 \
 	febootstrap-to-initramfs.8
 
@@ -78,6 +86,16 @@ febootstrap-run.8: febootstrap-run.pod
 febootstrap-run.txt: febootstrap-run.pod
 	pod2text $< > $@
 
+febootstrap-install.8: febootstrap-install.pod
+	pod2man \
+	  --section 8 \
+	  -c "Virtualization Support" \
+	  --release "$(PACKAGE_NAME)-$(PACKAGE_VERSION)" \
+	  $< > $@
+
+febootstrap-install.txt: febootstrap-install.pod
+	pod2text $< > $@
+
 febootstrap-minimize.8: febootstrap-minimize.pod
 	pod2man \
 	  --section 8 \
@@ -107,6 +125,8 @@ EXTRA_DIST = \
 	  febootstrap.sh \
 	febootstrap-run.8 febootstrap-run.txt febootstrap-run.pod \
 	  febootstrap-run.sh \
+	febootstrap-install.8 febootstrap-install.txt febootstrap-install.pod \
+	  febootstrap-install.sh \
 	febootstrap-minimize.8 febootstrap-minimize.txt \
 	  febootstrap-minimize.pod \
 	  febootstrap-minimize.sh \
diff --git a/febootstrap-install.pod b/febootstrap-install.pod
new file mode 100644
index 0000000..f48d217
--- /dev/null
+++ b/febootstrap-install.pod
@@ -0,0 +1,49 @@
+=head1 NAME
+
+febootstrap-install - Install a file in an febootstrap root filesystem
+
+=head1 SYNOPSIS
+
+ febootstrap-install ROOT LOCALFILE TARGETPATH MODE OWNER[.GROUP]
+
+=head1 DESCRIPTION
+
+This can be used to safely install a new file in an febootstrap
+root filesystem.
+
+Just copying a file in isn't usually safe, for reasons which are to do
+with the L<fakeroot(8)> command.
+
+=head1 EXAMPLE
+
+ febootstrap-install initramfs new-fstab /etc/fstab ugo=rw root.root
+
+=head1 SEE ALSO
+
+L<febootstrap(8)>,
+L<febootstrap-run(8)>,
+L<fakeroot(1)>,
+L<fakechroot(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones <rjones @ redhat . com>
+
+=head1 COPYRIGHT
+
+(C) Copyright 2009 Red Hat Inc.,
+L<http://et.redhat.com/~rjones/febootstrap>.
+
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
diff --git a/febootstrap-install.sh b/febootstrap-install.sh
new file mode 100755
index 0000000..76b99b7
--- /dev/null
+++ b/febootstrap-install.sh
@@ -0,0 +1,45 @@
+#!/bin/bash -
+# febootstrap-install
+# (C) Copyright 2009 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Written by Richard W.M. Jones <rjones@redhat.com>
+
+unset CDPATH
+
+usage ()
+{
+    echo "Usage: febootstrap-install ROOT LOCALFILE TARGETPATH MODE OWNER[.GROUP]"
+    echo "Please read febootstrap-install(8) man page for more information."
+}
+
+if [ $# != 5 ]; then
+    usage
+    exit 1
+fi
+
+set -e
+
+# This is a carefully chosen sequence of commands which
+# tries not to disturb any inode numbers apart from the
+# one for the new file.
+cp "$2" "$1"/"$3"
+ino=$(ls -i "$1"/"$3" | awk '{print $1}')
+cp "$1"/fakeroot.log "$1"/fakeroot.log.old
+grep -v "ino=$ino," "$1"/fakeroot.log.old > "$1"/fakeroot.log
+rm "$1"/fakeroot.log.old
+febootstrap-run "$1" -- chmod "$4" "$3"
+febootstrap-run "$1" -- chown "$5" "$3"
diff --git a/febootstrap-minimize.sh b/febootstrap-minimize.sh
index 148de5c..efee844 100755
--- a/febootstrap-minimize.sh
+++ b/febootstrap-minimize.sh
@@ -176,43 +176,48 @@ trap remove_tmpdir EXIT
 
 #----------------------------------------------------------------------
 
+# ***NOTE*** Wildcards cannot be passed to febootstrap-run.
+
 if [ "$keep_locales" != "yes" ]; then
-    rm -rf "$target"/usr/lib/locale/*
-    rm -rf "$target"/usr/share/locale
-    rm -rf "$target"/usr/lib*/gconv
-    rm -f "$target"/usr/bin/localedef
-    rm -f "$target"/usr/sbin/build-locale-archive
+    febootstrap-run "$target" -- rm -rf usr/lib/locale
+    febootstrap-run "$target" -- rm -rf usr/share/locale
+    febootstrap-run "$target" -- rm -rf usr/lib/gconv usr/lib64/gconv
+    febootstrap-run "$target" -- rm -f usr/bin/localedef
+    febootstrap-run "$target" -- rm -f usr/sbin/build-locale-archive
 fi
 
 if [ "$keep_docs" != "yes" ]; then
-    rm -rf "$target"/usr/share/man
-    rm -rf "$target"/usr/share/doc
-    rm -rf "$target"/usr/share/info
-    rm -rf "$target"/usr/share/gnome/help
+    febootstrap-run "$target" -- rm -rf usr/share/man
+    febootstrap-run "$target" -- rm -rf usr/share/doc
+    febootstrap-run "$target" -- rm -rf usr/share/info
+    febootstrap-run "$target" -- rm -rf usr/share/gnome/help
 fi
 
 if [ "$keep_cracklib" != "yes" ]; then
-    rm -rf "$target"/usr/share/cracklib
+    febootstrap-run "$target" -- rm -rf usr/share/cracklib
 fi
 
 if [ "$keep_i18n" != "yes" ]; then
-    rm -rf "$target"/usr/share/i18n
+    febootstrap-run "$target" -- rm -rf usr/share/i18n
 fi
 
 if [ "$keep_zoneinfo" != "yes" ]; then
     mv "$target"/usr/share/zoneinfo/{UCT,UTC,Universal,Zulu,GMT*,*.tab} \
       "$target"
-    rm -rf "$target"/usr/share/zoneinfo/*
+    febootstrap-run "$target" -- rm -rf usr/share/zoneinfo
+    febootstrap-run "$target" -- mkdir -p --mode=0755 usr/share/zoneinfo
     mv "$target"/{UCT,UTC,Universal,Zulu,GMT*,*.tab} \
       "$target"/usr/share/zoneinfo/
 fi
 
 if [ "$keep_rpmdb" != "yes" ]; then
-    rm -rf "$target"/var/lib/rpm/*
+    febootstrap-run "$target" -- rm -rf var/lib/rpm
+    febootstrap-run "$target" -- mkdir -p --mode=0755 var/lib/rpm
 fi
 
 if [ "$keep_yum_cache" != "yes" ]; then
-    rm -rf "$target"/var/cache/yum/*
+    febootstrap-run "$target" -- rm -rf var/cache/yum
+    febootstrap-run "$target" -- mkdir -p --mode=0755 var/cache/yum
 fi
 
 if [ "$keep_services" != "yes" ]; then
@@ -270,13 +275,14 @@ __EOF__
 fi
 
 if [ "$keep_sln" != "yes" ]; then
-    rm -f "$target"/sbin/sln
+    febootstrap-run "$target" -- rm -f sbin/sln
 fi
 
 if [ "$keep_ldconfig" != "yes" ]; then
-    rm -f "$target"/sbin/ldconfig
-    rm -f "$target"/etc/ld.so.cache
-    rm -rf "$target"/var/cache/ldconfig/*
+    febootstrap-run "$target" -- rm -f sbin/ldconfig
+    febootstrap-run "$target" -- rm -f etc/ld.so.cache
+    febootstrap-run "$target" -- rm -rf var/cache/ldconfig
+    febootstrap-run "$target" -- mkdir -p --mode=0755 var/cache/ldconfig
 fi
 
 if [ "$pack_executables" = "yes" ]; then
diff --git a/febootstrap-run.pod b/febootstrap-run.pod
index 9072f5a..d6440ca 100644
--- a/febootstrap-run.pod
+++ b/febootstrap-run.pod
@@ -39,20 +39,11 @@ contents still happen).
 
 =head1 EXAMPLES
 
-One way to create a new file with specific ownership and permissions
-from a script is to create new content for a file as C<root/file.new>:
+Remove a directory subtree safely:
 
- #!/bin/bash
-
- echo blah > root/file.new
-
-then "install" it with the correct ownership and permissions.
-
- febootstrap-run root -- install -m 0644 -o root -g root /file.new /file
- rm root/file.new
+ febootstrap-run initramfs -- rm -r /etc
 
-(This requires that the C</usr/bin/install> command from coreutils
-exists inside the root.)
+(This requires that you have a compatible 'rm' command in the root).
 
 Another way to do complex operations from a script is to export a
 function:
@@ -76,6 +67,7 @@ particular you may want to set:
 =head1 SEE ALSO
 
 L<febootstrap(8)>,
+L<febootstrap-install(8)>,
 L<fakeroot(1)>,
 L<fakechroot(1)>.
 
diff --git a/febootstrap-to-initramfs.sh b/febootstrap-to-initramfs.sh
index 6dfc3f9..849aa7b 100755
--- a/febootstrap-to-initramfs.sh
+++ b/febootstrap-to-initramfs.sh
@@ -32,6 +32,8 @@ if [ ! -f fakeroot.log -a $(id -u) -ne 0 ]; then
     exit 1
 fi
 
+set -e
+
 if [ -f fakeroot.log ]; then
     fakeroot -i fakeroot.log \
     sh -c 'find -not -name fakeroot.log -a -print0 | cpio -o0c | gzip --best'
diff --git a/febootstrap.pod b/febootstrap.pod
index 109f6a2..e208603 100644
--- a/febootstrap.pod
+++ b/febootstrap.pod
@@ -129,8 +129,8 @@ so that yum thinks it is running as root.  Fakeroot keeps track of
 directory as C<I<TARGET>/fakeroot.log>.
 
 This logfile is indexed by inode number, which makes certain
-operations safe and other operations unsafe.  For example, deleting
-files is usually safe.  Files should be replaced only by doing:
+operations safe and other operations unsafe.
+Files should be replaced only by doing:
 
  echo updated-content > old-file
 
@@ -139,9 +139,12 @@ files is usually safe.  Files should be replaced only by doing:
 Deleting files and then creating new ones (even with a different name)
 is usually unsafe, because the new files might reuse inodes claimed by
 the old files, and so appear with peculiar permissions
-(eg. unreadable, or as a symbolic link).  (XXX We need an
-C<febootstrap-install> utility to automate installing new files safely
-into a filesystem).
+(eg. unreadable, or as a symbolic link).
+
+Deleting files is also usually unsafe, although the reasons are more
+subtle.  If you just use C<rm> then the inode number is not deleted
+from C<fakeroot.log> which means it can be reused by another file
+later on.
 
 In most cases it's usually safest to use C<febootstrap-run>.
 
@@ -156,6 +159,11 @@ permissions.
 
 =item *
 
+Use L<febootstrap-install(8)> to install a file with permissions
+in the root filesystem.
+
+=item *
+
 Generate an initramfs (compressed cpio) file containing the correct
 permissions using the tool C<febootstrap-to-initramfs>.
 
@@ -218,6 +226,7 @@ L<http://et.redhat.com/~rjones/febootstrap>
 L<febootstrap-to-initramfs(8)>,
 L<febootstrap-minimize(8)>,
 L<febootstrap-run(8)>,
+L<febootstrap-install(8)>,
 L<fakeroot(1)>,
 L<fakechroot(1)>,
 L<yum(8)>,
-- 
1.8.3.1