rc: Move stop_boot() from rc.d/fsck to rc.subr and improve it
[dragonfly.git] / initrd / mkinitrd.sh
1 #!/bin/sh
2 #
3 # Copyright (c) 2010, 2018
4 #       The DragonFly Project.  All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # 1. Redistributions of source code must retain the above copyright
11 #    notice, this list of conditions and the following disclaimer.
12 # 2. Redistributions in binary form must reproduce the above copyright
13 #    notice, this list of conditions and the following disclaimer in
14 #    the documentation and/or other materials provided with the
15 #    distribution.
16 # 3. Neither the name of The DragonFly Project nor the names of its
17 #    contributors may be used to endorse or promote products derived
18 #    from this software without specific, prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
24 # COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
30 # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 # SUCH DAMAGE.
32 #
33
34 #
35 # Description
36 #
37 # This tool packs the (statically linked) rescue tools (at /rescue by
38 # default) and contents specified by "-c <content_dirs>", such as the
39 # necessary etc files, into an UFS-formatted VN image.  This image is
40 # installed at /boot/kernel/initrd.img.gz and used as the initial ramdisk
41 # to help mount the real root filesystem when it is encrypted or on LVM.
42 #
43
44
45 #
46 # Default configurations
47 #
48
49 # Directory hierarchy on the initrd
50 #
51 # * 'new_root' will always be created
52 # * 'sbin' will be symlinked to 'bin'
53 # * 'tmp' will be symlinked to 'var/tmp'
54 #
55 INITRD_DIRS="bin dev etc mnt var"
56
57 # Directory of the statically linked rescue tools which will be copied
58 # onto the initrd.
59 RESCUE_DIR="/rescue"
60
61 # Specify the location that the initrd will be installed to, i.e.,
62 # <BOOT_DIR>/kernel/initrd.img.gz
63 BOOT_DIR="/boot"
64
65 # Maximum size (number of MB) allowed for the initrd image
66 INITRD_SIZE_MAX="15"  # MB
67
68 # When run from the buildworld/installworld environment do not require that
69 # things like uniq, kldload, mount, newfs, etc be in the cross-tools.
70 # These must run natively for the current system version.
71 #
72 PATH=${PATH}:/sbin:/usr/sbin:/bin:/usr/bin
73
74 #
75 # Helper functions
76 #
77
78 log() {
79         echo "$@" >&2
80 }
81
82 error() {
83         local rc=$1
84         shift
85         log "$@"
86         exit ${rc}
87 }
88
89 check_dirs() {
90         for _dir; do
91                 [ -d "${_dir}" ] ||
92                     error 1 "Directory '${_dir}' does not exist"
93         done
94         return 0
95 }
96
97 # Calculate the total size of the given directory, taking care of the
98 # hard links.
99 #
100 # NOTE: Do not use 'du' since it gives the disk usage of the files,
101 #       which varies between different filesystems.
102 #
103 calc_size() {
104         find "$1" -ls | \
105             awk '{ print $7,$1 }' | \
106             sort -n -k 2 | \
107             uniq -f 1 | \
108             awk '{ sum+=$1 } END { print sum }'  # byte
109 }
110
111
112 #
113 # Functions
114 #
115
116 calc_initrd_size() {
117         log "Calculating required initrd size ..."
118         isize=0
119         for _dir; do
120                 csize=$(calc_size ${_dir})
121                 log "* ${_dir}: ${csize} bytes"
122                 isize=$((${isize} + ${csize}))
123         done
124         # Round initrd size up by MB
125         isize_mb=$(echo ${isize} | awk '
126             function ceil(x) {
127                 y = int(x);
128                 return (x>y ? y+1 : y);
129             }
130             {
131                 mb = $1/1024/1024;
132                 print ceil(mb);
133             }')
134         # Reserve another 1 MB for advanced user to add custom files to the
135         # initrd without creating it from scratch.
136         echo $((${isize_mb} + 1))
137 }
138
139 create_vn() {
140         kldstat -qm vn || kldload -n vn ||
141             error 1 "Failed to load vn kernel module"
142
143         VN_DEV=$(vnconfig -c -S ${INITRD_SIZE}m -Z -T vn ${INITRD_FILE}) &&
144             echo "Configured ${VN_DEV}" ||
145             error 1 "Failed to configure VN device"
146
147         newfs -i 131072 -m 0 /dev/${VN_DEV}s0 &&
148             echo "Formatted initrd image with UFS" ||
149             error 1 "Failed to format the initrd image"
150         mount_ufs /dev/${VN_DEV}s0 ${BUILD_DIR} &&
151             echo "Mounted initrd image on ${BUILD_DIR}" ||
152             error 1 "Failed to mount initrd image on ${BUILD_DIR}"
153 }
154
155 destroy_vn() {
156         umount /dev/${VN_DEV}s0 &&
157             echo "Unmounted initrd image" ||
158             error 1 "Failed to umount initrd image"
159         vnconfig -u ${VN_DEV} &&
160             echo "Unconfigured ${VN_DEV}" ||
161             error 1 "Failed to unconfigure ${VN_DEV}"
162 }
163
164 make_hier() {
165         mkdir -p ${BUILD_DIR}/new_root ||
166             error 1 "Failed to mkdir ${BUILD_DIR}/new_root"
167         # Symlink 'sbin' to 'bin'
168         ln -sf bin ${BUILD_DIR}/sbin
169         # Symlink 'tmp' to 'var/tmp', as '/var' will be mounted with
170         # tmpfs, saving a second tmpfs been mounted on '/tmp'.
171         ln -sf var/tmp ${BUILD_DIR}/tmp
172         for _dir in ${INITRD_DIRS}; do
173                 [ ! -d "${BUILD_DIR}/${_dir}" ] &&
174                     mkdir -p ${BUILD_DIR}/${_dir}
175         done
176         echo "Created directory structure"
177 }
178
179 copy_rescue() {
180         cpdup -o -u ${RESCUE_DIR}/ ${BUILD_DIR}/bin/ &&
181             echo "Copied ${RESCUE_DIR} to ${BUILD_DIR}/bin" ||
182             error 1 "Failed to copy ${RESCUE_DIR} to ${BUILD_DIR}/bin"
183 }
184
185 copy_content() {
186         for _dir in ${CONTENT_DIRS}; do
187                 cpdup -o -u ${_dir}/ ${BUILD_DIR}/ &&
188                     echo "Copied ${_dir} to ${BUILD_DIR}" ||
189                     error 1 "Failed to copy ${dir} to ${BUILD_DIR}"
190         done
191 }
192
193 print_info() {
194         lt ${BUILD_DIR}
195         df -h ${BUILD_DIR}
196 }
197
198 # Check the validity of the created initrd image before moving over.
199 # This prevents creating an empty and broken initrd image by running
200 # this tool but without ${CONTENT_DIRS} prepared.
201 #
202 # NOTE: Need more improvements.
203 #
204 check_initrd()
205 {
206         [ -x "${BUILD_DIR}/sbin/oinit" ] &&
207         [ -x "${BUILD_DIR}/bin/sh" ] &&
208         [ -x "${BUILD_DIR}/etc/rc" ] || {
209                 destroy_vn
210                 error 1 "Ivalid initrd image!"
211         }
212 }
213
214 usage() {
215         error 2 \
216             "usage: ${0##*/} [-b boot_dir] [-r rescue_dir]" \
217             "[-s size] [-S max_size] -c <content_dirs>"
218 }
219
220
221 #
222 # Main
223 #
224
225 while getopts :b:c:hr:s:S: opt; do
226         case ${opt} in
227         b)
228                 BOOT_DIR="${OPTARG}"
229                 ;;
230         c)
231                 CONTENT_DIRS="${OPTARG}"
232                 ;;
233         h)
234                 usage
235                 ;;
236         r)
237                 RESCUE_DIR="${OPTARG}"
238                 ;;
239         s)
240                 INITRD_SIZE="${OPTARG}"
241                 ;;
242         S)
243                 INITRD_SIZE_MAX="${OPTARG}"
244                 ;;
245         \?)
246                 log "Invalid option -${OPTARG}"
247                 usage
248                 ;;
249         :)
250                 log "Option -${OPTARG} requires an argument"
251                 usage
252                 ;;
253         esac
254 done
255
256 shift $((OPTIND - 1))
257 [ $# -ne 0 ] && usage
258 [ -z "${BOOT_DIR}" -o -z "${RESCUE_DIR}" -o -z "${CONTENT_DIRS}" ] && usage
259 check_dirs ${BOOT_DIR} ${RESCUE_DIR} ${CONTENT_DIRS}
260
261 VN_DEV=""
262 INITRD_SIZE=${INITRD_SIZE%[mM]}  # MB
263 INITRD_SIZE_MAX=${INITRD_SIZE_MAX%[mM]}  # MB
264
265 BUILD_DIR=$(mktemp -d -t initrd) || error $? "Cannot create build directory"
266 echo "Initrd build directory: ${BUILD_DIR}"
267 INITRD_FILE="${BUILD_DIR}.img"
268 INITRD_DEST="${BOOT_DIR}/kernel/initrd.img.gz"
269
270 CSIZE=$(calc_initrd_size ${RESCUE_DIR} ${CONTENT_DIRS})
271 echo "Required initrd image size: ${CSIZE} MB"
272 if [ -n "${INITRD_SIZE}" -a "${INITRD_SIZE}" != "0" ]; then
273         if [ ${CSIZE} -gt ${INITRD_SIZE} ]; then
274                 error 1 "Given initrd size (${INITRD_SIZE} MB) too small"
275         fi
276 else
277         INITRD_SIZE=${CSIZE}
278 fi
279 echo "Initrd size: ${INITRD_SIZE} MB"
280
281 if [ -n "${INITRD_SIZE_MAX}" -a "${INITRD_SIZE_MAX}" != "0" ] && \
282    [ ${INITRD_SIZE} -gt ${INITRD_SIZE_MAX} ]; then
283         error 1 "Exceeded the maximum size (${INITRD_SIZE_MAX} MB)"
284 fi
285
286 create_vn
287 make_hier
288 copy_rescue
289 copy_content
290 print_info
291 destroy_vn
292 rm -rf ${BUILD_DIR}
293
294 echo -n "Compressing ${INITRD_FILE} ..."
295 gzip -9 ${INITRD_FILE}
296 echo " OK"
297
298 if [ -f "${INITRD_DEST}" ]; then
299         echo -n "Backing up ${INITRD_DEST} ..."
300         mv ${INITRD_DEST} ${INITRD_DEST}.old
301         echo " OK (${INITRD_DEST}.old)"
302 fi
303
304 echo -n "Installing ${INITRD_FILE}.gz to ${INITRD_DEST} ..."
305 install -o root -g wheel -m 444 ${INITRD_FILE}.gz ${INITRD_DEST}
306 echo " OK"
307 rm -f ${INITRD_FILE}.gz