aboutgitcode
path: root/mbuto
blob: 247b63de46fd73170be5be0d7b96f3959294110b (plain) (tree)
1
2
3
4
5
6
7
8
9
             




                                            
                                      

                                                      

                                                                       

                                                                       












                                                                                
                                                    
 





                                                                           
                            
 
                                     
 

                                                                 






                                                                    


                                                                           
                                                   
                                     
 
                                                                         
 

                        
 
                      

                     
                          

              
                            
 






                                                          




                                                                          
                                                         


























                                                                              
                                  

























                                                                               
 


















                                                                      
                       


                                                                         

 

                                             
                                                                           
                                                                           

                                                                      
                                                       



                                                

                                               

                                                       
                           


                                          
                                      
 
                       


                             



                                 

 















                                                                                




                                                        







                                                                                             







                                                                         



                                                                               
                                    



                                                                   
                                                                  
 
                                                         
 

                                                                           
 
                                                   

                                                                                
 





                                                                            
                                                                         

                                                                     
 
              
                                     

                                                                              
                                                                                
                                                                          
                                                                                 
 
                                                                                 
 
                                                                    


                                                         

                                                                             
 
                                                      

                                                                                 

                  










                                               
                                                            
 
                   

                                                                                 
 
                         

                                           





                                                                        





                                                                               

 
                                                                             








                                                      





                                                                                
                                                                                
                                                                             
                                                                                
                        




































                                                                           
                           


                                                                    
                


















                                                                             
                                                                           

















                                                                                


                                                               
          
                                        


                                   











                                                                            














                                                                          













                                                                            


                                                                                


                                                                               

                                                                                           
                                                                                  










                                                                           
                     





































                                                                             
                                                    

 
                                                               

                                                   
                                                                           




                                    




                                                                          


                     
             

 

                                                                                
                                                                              
                             
                   
                                                  
                           















































                                                                                
                                                      



















                                                                                
 
                            




























                                                                                














                                                                            
                                                                              
                                       
               













                                                                                
                                           











                                                                    

                               










                                                                            








                                                              







                                                                                

                                                             












                                                      








                                                                        

                 












                                                                                
                 
                               

                                                                     

                                                                            
                                                
                         

            
                            






























                                                                                
                                                        







                                                       
                                   
                           

                                                    
                                   


                                                   

                                                               
 
                      




                                     

                                                        


                                                                                               
                                                                                  





                                                                                      
                                  
 
                                                  

                          
            






                                                                      
                                          

 











                                                                                
































                                                                                
                                                
















                                                                         
                                                









































                                                                                














                                                                             
                               
                       
                               
                         
            

                                





                                                                  











                                        

                 

                                                                          



                                                                     

                                                                                











                                                  
                                                                         

                  
      





                                                                   

                                                                       












                                                                            





                                                        
                                            
 
         
                               


                                                                


             


                                                                           







                                               
                                                                        







                                                                    

                                                                           
                                                                           





                                                                                
                                      

                                                                            

                                                                   




                                                                            
                                                     
                                                                            
                                                                  
                                                                             




                                                                       


                                                                            







                                                                             




                                                                             


                                            

                                                                               
                      
                                                           










                                                                              
                                                         






                                                                                
                                                    







                                                                               
                                                    

                                      

                                                                
                         
          
  
 




                                            
               
                                        
                        
                                                                  
                                                                  







                                                                  


                                                                    
                                                                  


                     

                                     




                                                         


                          






                                                                        


                                                                       




                 
#!/bin/sh -ef
#
# mbuto: Minimal Builder Using Terse Options
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Copyright (c) 2020-2022 Red Hat GmbH
# Authors: Stefano Brivio <sbrivio@redhat.com>
#          Sevinj Aghayeva <sevinj.aghayeva@gmail.com>
#
# This script builds Linux initramfs images suitable for lightweight VM
# environments, without relying on distribution-specific tools (dracut,
# debootstrap, mkinitramfs, etc.) or containerised environments.
#
# Programs are sourced from the host, together with required dynamic libraries.
# Kernel modules, links, and initial device nodes are configured manually. A
# convenience support for distribution packages is supplied, with the sole
# function of extracting packages, ignoring dependencies, without an actual
# installation process.
#
# shellcheck disable=SC2016,SC2046,SC2048,SC2059,SC2086,SC2154
# 		     ^ FIXUP template, word splitting helpers, printf wrappers,
#		       arguments to du -c, cmd_check assigning via eval

### Configuration ##############################################################

# Programs: see profile_base() below for an example.

# Libraries commonly loaded via dlopen(3) (strictly needed for basic tasks)
LIBS_DLOPEN="${LIBS_DLOPEN:-
	libc.so.6		libnss_dns.so.2
	libc.so.6		libnss_files.so.2
}"

# Links: see profile_base().

# Kernel modules: see profile_base().

# Device nodes, NAME,TYPE,MAJOR,MINOR supported, copied otherwise
NODES="${NODES:-console kmsg null ptmx random urandom zero}"

# Empty directories to create
DIRS="${DIRS:-/proc /sys}"

# Copies of full paths, attributes and parents directories preserved
COPIES="${COPIES:-}"

# Workers for time-consuming tasks such as stripping modules, see workers()
THREADS="$(nproc)"

# Fix-up script to run before /init, can be omitted
[ -z "${FIXUP}" ] && FIXUP='#!/bin/sh

export PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/sbin:/usr/sbin:

mount -t proc proc /proc
mount -t sysfs sys /sys

for m in __KMODS__; do
	modprobe ${m}
done
mount -t devtmpfs dev /dev

mkdir /dev/pts
mount -t devpts pts /dev/pts

# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=968199
ln -sf /proc/self/fd /dev/fd
ln -sf /dev/fd/0 /dev/stdin
ln -sf /dev/fd/1 /dev/stdout
ln -sf /dev/fd/2 /dev/stderr
'

# Start a shell, nothing else
profile_base() {
	# Programs sourced with linked libraries, alternatives with commas
	PROGS="${PROGS:-ash,dash,bash mount mkdir ln}"

	# Links: installed target program, then link name
	LINKS="${LINKS:-
		ash,dash,bash		/bin/sh
		ash,dash,bash		/init}"

	# List of kernel modules
	KMODS="${KMODS:-}"

	FIXUP="${FIXUP}
		sh +m
"

	# Output (stdout) template keywords: __INITRD__ and __KERNEL__
	OUTPUT="__INITRD__
"
}

# Profile spawning bash, for general usage
profile_bash() {
	PROGS="${PROGS:-bash cat chmod lsmod modprobe find grep mkdir mknod mv
	       ls ip ln rm mount umount ps strace}"

	LINKS="${LINKS:-
		bash			/bin/sh
		bash			/init}"

	KMODS="${KMODS:-virtio_net virtio_pci}"

	NODES="${NODES} tty ttyS0"

	OUTPUT="__INITRD__
"
}

# Base profile for Kata Containers (https://katacontainers.io/)
profile_kata() {
	PROGS="${PROGS:-kata-agent ash,dash,bash mount modprobe mkdir ln}"

	KMODS="${KMODS:-vmw_vsock_virtio_transport virtio_net virtiofs vfio
	       virtio_pci}"
	LINKS="${LINKS:-
		ash,dash,bash		/bin/sh
		kata-agent		/init}"

	FIXUP="${FIXUP}"'
		mkdir -p /sys/fs/cgroup
		mount -t tmpfs cgroup /sys/fs/cgroup
		mkdir /sys/fs/cgroup/unified
		mount -o rw,nsdelegate -t cgroup2 cgroup /sys/fs/cgroup/unified

		for t in cpu,cpuacct blkio memory perf_event pids cpuset \
			 freezer devices; do
			mkdir /sys/fs/cgroup/${t}
			mount -o rw,${t} -t cgroup cgroup /sys/fs/cgroup/${t}
		done
'

	OUTPUT="Kata Containers [hypervisor.qemu] configuration:

	kernel = \"__KERNEL__\"
	initrd = \"__INITRD__\"
"
}

# Debugging profile for Kata Containers: start shell before kata-agent
profile_kata_debug() {
	PROFILE_LIST="${PROFILE_LIST} kata_debug"

	profile_kata

	PROGS="${PROGS} ash,dash,bash cat ip ls strace insmod nsenter"

	LINKS="${LINKS}
		ash,dash,bash		/bin/sh"

	FIXUP="${FIXUP}
		echo Starting interactive shell, exit to spawn kata-agent
		sh +m
"
}

# Profile for passt (https://passt.top) tests
profile_passt() {
	PROGS="${PROGS:-ash,dash,bash ip mount ls insmod mkdir ln cat chmod
	       lsmod modprobe find grep mknod mv rm umount udhcpc jq iperf3
	       dhclient busybox logger sed tr chown sipcalc cut md5sum
	       nc dd strace ping tail killall sleep sysctl nproc
	       tcp_rr tcp_crr udp_rr which tee seq bc}"

	KMODS="${KMODS:- virtio_net virtio_pci}"

	LINKS="${LINKS:-
		 ash,dash,bash		/init
		 ash,dash,bash		/bin/sh
		 ash,dash,bash		/usr/bin/bash}"

	DIRS="${DIRS} /tmp"

	COPIES="${COPIES}
		/etc/udhcpc/default.script
		/sbin/dhclient-script"

	FIXUP="${FIXUP}
		:> /etc/fstab
		sh +m
"

	OUTPUT="KERNEL=__KERNEL__
INITRD=__INITRD__
"
}

# Profile for kselftests (https://kselftest.wiki.kernel.org/)
profile_kselftests() {
	PROGS="${PROGS:-addr2line awk basename bash bc bridge cat chmod chown cp
	       cpupower cut date dd diff dirname dmesg du env ethtool find gcc
	       grep head id ifconfig insmod ip ip6tables iperf3 iptables ipvsadm
	       jq killall ln logger ls lsmod make mausezahn md5sum mkdir mknod
	       mktemp modprobe mount mv nc nft nproc nstat pidof ping python3
	       realpath rm rmdir sed seq sipcalc sleep socat sort ss strace
	       sysctl tail taskset tc tee timeout tput tr traceroute traceroute6
	       true umount uname uniq uuidgen wc which}"

	if [ ! -f "include/config/kernel.release" ]; then
		err "This profile needs to run from a kernel tree, exiting"
		exit 1
	fi

	KERNEL="$(${CAT} include/config/kernel.release)"

	__testpath="./tools/testing/selftests/"
	__makefile="${__testpath}Makefile"
	__colls=

	if [ -n "${SUBOPT_list+x}" ]; then
	    __colls=$("${AWK}" '/^TARGETS \+= / {print $3}' ${__makefile} | "${TR}" '\n' ' ')
	    printf "Available collections:\n   "
	    echo "${__colls}" | "${FMT}" -t
	    exit 0
	fi

	for __c in ${SUBOPT_collection}; do
		if ! ${GREP} -q "^TARGETS += ${__c}$" ${__makefile}; then
			notice "WARNING: collection ${__c} doesn't exist"
			continue
		fi
		__colls="${__c} ${__colls}"
	done

	for __t in ${SUBOPT_test}; do
		__colls="${__colls} $(echo ${__t} | ${AWK} -F':' '{print $1}')"
	done

	if [ -z "${__colls}" ]; then
		__colls="$(${SED} -n 's/^TARGETS += \(.*\)$/\1/p' \
			   "${__makefile}")"
	fi

	__colls="$(echo ${__colls} | ${TR} ' ' '\n' | ${SORT} -u)"

	__pattern="$(list_join "${__colls}" '^' '$' '|')"

	__skip_targets="$(${AWK} '/^TARGETS/ { print $3}' ${__makefile} | \
		${EGREP} -v "${__pattern}" | ${TR} '\n' ' ')"

	${RM} -rf "${__testpath}/kselftest_install"
	${MAKE} SKIP_TARGETS="${__skip_targets}" -C ${__testpath} install \
								 >/dev/null 2>&1

	MODDIR="$(${REALPATH} .mbuto_mods)"
	${RM} -rf "${MODDIR}"
	${MKDIR} -p "${MODDIR}"
	INSTALL_MOD_PATH="${MODDIR}" ${MAKE} modules_install -j ${THREADS} \
								>/dev/null

	__files="$(list_join "${__colls}" "${__testpath}" '/config' ' ')"
	__mods="$(${CAT} ${__files} 2>/dev/null | \
			${AWK} -F'=' '/=m/ {print $1}' | ${SORT} -u)"

	KMODS=
	if [ ! -z "${__mods}" ]; then
		__pattern="$(list_join "${__mods}" '^obj-\$\(' '\).*.o$' '|')"
		__result="$(${BASENAME} -a -s .o \
			   $(${EGREP} -rn --include "*Makefile" ${__pattern} | \
			   ${TR} -d '+' | ${AWK} -F'=' '/=/ {print $2}'))"
		__find_pattern="$(list_join "${__result}" '-name ' '.ko ' '-o ')"

		KMODS="$(${FIND} "${MODDIR}" ${__find_pattern} | ${TR} '\n' ' ')"

		[ "${NOSTRIP}" != "y" ] && workers kmod_strip_worker

		KMODS="$(${BASENAME} -a -s .ko ${KMODS})"

		__kmods_needed="$(list_join "${__result}" '' '' ' ')"
		__kmods_missing="$(list_diff "${__kmods_needed}" "${KMODS}")"

		if [ ! -z "${__kmods_missing}" ]; then
			printf "WARNING: the following modules are missing:\n   "
			echo "${__kmods_missing}" | "${FMT}" -t
		fi
	fi

	LINKS="${LINKS:-
		 bash		/init
		 bash		/bin/sh
		 bash		/bin/bash
		 bash		/usr/bin/bash}"

	NODES="${NODES} tty ttyS0"

	DIRS="${DIRS} /tmp /run/netns /var/run"

	COPIES="${COPIES} ${__testpath}kselftest_install/*,"

	__run_args=
	[ ! -z "${SUBOPT_collection}" ] && __run_args="-c ${SUBOPT_collection}"
	[ ! -z "${SUBOPT_test}" ] && __run_args="${__run_args} -t ${SUBOPT_test}"

	FIXUP="${FIXUP}"'
		mkdir /dev/shm
		mount -t tmpfs shm /dev/shm

		set +m
		:> /etc/fstab
		echo 3 > /proc/sys/kernel/printk
		echo "Press s for shell, any other key to run selftests"
		read a
		if [ "${a}" != "s" ]; then
			if ./run_kselftest.sh '"${__run_args}"'; then
				echo "All tests passed, shutting down guest..."
				echo o > /proc/sysrq-trigger
			fi
		fi
'

	for __f in $(${FIND} ${__testpath}kselftest_install/ -executable); do
		case $("${FILE}" -bi "${__f}") in
		"application/"*) libs_copy "${__f}" ;;
		esac
	done

	OUTPUT="__INITRD__
"
}

################################################################################


### Helpers ####################################################################

# List of tools used here, assigned to uppercase variable names
TOOLS="awk basename bc cat cd chmod cp cpio depmod diff dirname du file find fmt
       grep egrep gzip ldconfig ldd ln ls make mkdir mknod mktemp modprobe mv
       printf readlink realpath rm rmdir sed seq sleep sort strip sync tr umount
       uname wget which"

# err() - Print error and exit
# $@:	Error message, optionally with printf format and arguments
err() {
	( printf "Error: "; printf "$@"; echo ) 1>&2
	exit 1
}

# warn() - Warn to standard error
# $@:	Warning message, optionally with printf format and arguments
warn() {
	( printf "Warning: "; printf "$@"; echo ) 1>&2
}

# notice() - Print notice-level messages
# $@:	Notice, optionally with printf format and arguments, can be empty
notice() {
	( { [ -z "${1}" ] && echo; } || { printf "$@"; echo; } ) 1>&2
}

# info() - Print informational messages for verbose operation
# $@:	Message, optionally with printf format and arguments
info() {
	{ [ "${VERBOSE}" = "y" ] && ( { printf "$@"; echo; } ) 1>&2; } || :
}

# cmd_check() - Check that a command exists, assign to uppercase variable
# $1:	Command name, can be an alias
cmd_check() {
	if ! eval $(echo "${1}" |
		   tr 'a-z-' 'A-Z_')="\$(command -v ${1})"; then
		err "${1} not found"
	fi
}

# cleanup() - Remove left-overs on exit, used from trap
cleanup() {
	"${RM}" -rf "${wd}"
	[ -n "${pkg_tmp}" ] && "${RM}" -f "${pkg_tmp}"
	[ -n "${compress_test1}" ] && "${RM}" -f "${compress_test1}"
	[ -n "${compress_test2}" ] && "${RM}" -f "${compress_test2}"
	return 0
}

# dir_size() - Print summed size of directories with SI suffix
# $*:	Paths, can be names of files and directories, mixed
dir_size() {
	IFS='
'
	for __l in $("${CD}" "${wd}"; "${DU}" -c --si "${@}" 2>/dev/null); do
		__last_size="${__l}"
	done
	echo "${__last_size%[ 	]*}"
	unset IFS
}

# strip_all() - Strip all executables in working directory, $wd
strip_all() {
	for __f in $("${FIND}" "${wd}"); do
		case $("${FILE}" -bi "${__f}") in
		"application/x-sharedlib;"*|"application/x-executable;"*)
			"${STRIP}" -s "${__f}" -R .comment 2>/dev/null || :
			;;
		*)
			;;
		esac
	done
}

# fixup_apply() - Apply fix-up script to /init, from $FIXUP or passed in options
fixup_apply() {
	[ -n "${SCRIPT}" ] && [ "${SCRIPT}" = "-" ] && return
	[ -z "${SCRIPT}" ] && [ -z "${FIXUP}" ] && return

	__call="$("${READLINK}" "${wd}/init")"
	rm "${wd}/init"

	if [ -n "${SCRIPT}" ]; then
		"${CP}" "${SCRIPT}"
	else
		KMODS="$(echo ${KMODS} | tr -d '\n')"
		printf "%s" "${FIXUP}" | \
		sed 's,__KMODS__,'"${KMODS}"',g' > "${wd}/init"
	fi
	echo "${__call}" >> "${wd}/init"
	"${CHMOD}" 755 "${wd}/init"
}

# workers() - Run $THREADS instances of $1 in subshells, wait for completion
# $1:	Function to call
workers() {
	__sync_dir="$(${MKTEMP} -d)"
	for __t in $(${SEQ} 1 ${THREADS}); do
		${MKDIR} "${__sync_dir}/${__t}"
		( ${1} ${__t}; ${RMDIR} "${__sync_dir}/${__t}"; ) &
	done

	while ! ${RMDIR} "${__sync_dir}" 2>/dev/null; do ${SLEEP} 1; done
}

# list_diff() - Given two lists, $1 and $2, print tokens from $1 not in $2
# $1:	List with elements to be checked
# $2:	List to check against
list_diff() {
	__diff=
	for __e1 in ${1}; do
		__found=0
		for __e2 in ${2}; do
			[ ${__e1} = ${__e2} ] && __found=1
		done
		[ ${__found} -eq 0 ] && __diff="${__e1} ${__diff}"
	done
	printf '%s' "${__diff}"
}

# list_join() - Add prefix and suffix, with separator, to all tokens in list
# $1:	List of tokens
# $2:	Prefix
# $3:	Suffix
# $4:	Separator, used after prefix and before suffix
list_join() {
	__s=
	for __t in ${1}; do
		__s="${2}${__t}${3}${4}${__s}"
	done
	__s=${__s%${4}}
	printf '%s' "${__s}"
}

################################################################################


### Suboption Parsing #########################################################

SUBOPTS='
	kselftests	C	collection	2	Select a collection of kernel tests
	kselftests	T	test		2	Select a test from a collection
	kselftests	L	list		1	List available collections
'

subopts_profile=
# subopts_usage() - Print suboption usage for a single, passed SUBOPTS line
# $1:	Profile name from SUBOPTS line
# $2:	Short option name from SUBOPTS line
# $3:	Unused
# $4:	Option description from SUBOPTS line
subopts_usage_one() {
	__profile="${1}"
	__short="${2}"
	__help="${5}"

	if [ "${usage_subopts_profile}" != ${__profile} ]; then
		usage_subopts_profile="${__profile}"
		printf "\tSub-options for profile ${__profile}:\n"
	fi

	printf "\t\t-%s: %s\n" ${__short} "${__help}"
}

# subopts_usage() - Print suboption usage for all SUBOPTS
subopts_usage() {
	IFS='
'
	for __line in ${SUBOPTS}; do
		IFS='	'
		subopts_usage_one ${__line}
		IFS='
'
	done
	unset IFS
}

# subopts_get_one() - Sub-option value from arguments, check one SUBOPTS line
# $1:	Short option name as passed on command line
# $2:	Value passed on command line
# $3:	Profile name from SUBOPTS
# $4:	Short option name from SUBOPTS
# $5:	Long option name from SUBOPTS, used in SUBOPT_name assignment
subopts_get_one() {
	__passed="${1}"
	__value="${2}"
	__profile="${3}"
	__short="${4}"
	__name="${5}"

	[ "${__profile}" != "${PROFILE}" ] 	&& return 1
	[ "${__passed}" != "${__short}" ]	&& return 1

	eval $(echo SUBOPT_${__name}=\"${__value}\")
}

# subopts_get() - Parse sub-options, validating against SUBOPTS
# $1:	Short option name as passed on command line
# $2:	Value passed on command line
# Return: argument count including option switch, doesn't return on failure
subopts_get() {
	IFS='
'
	for __line in ${SUBOPTS}; do
		IFS='	'
		if subopts_get_one "${1}" "${2}" ${__line}; then
			unset IFS
			return $(echo "${__line}" | "${AWK}" '{print $4}')
		fi

		IFS='
'
	done
	usage
}

### CPIO #######################################################################

# compress_select() - Try compressors and pick the fastest to decompress image
# $1:	Existing CPIO archive
compress_select() {
	if [ ! -f "/boot/config-${KERNEL}" ]; then
		echo "gzip"
		return
	fi

	compress_test1="$("${MKTEMP}")"
	compress_test2="$("${MKTEMP}")"

	__min_time=
	for __a in gzip lz4 lzo lzma xz; do
		[ "${__a}" = "lzo" ] && __cmd="lzop" || __cmd="${__a}"
		__kcfg="CONFIG_RD_$(echo "${__a}" | tr '[:lower:]' '[:upper:]')"

		if ! command -v "${__cmd}" > /dev/null 2>&1 ||
		   ! "${GREP}" -q "${__kcfg}" "/boot/config-${KERNEL}"; then
			continue
		fi

		"${__cmd}" -q -9 -c "${1}" > "${compress_test1}"

		__start=$("${CAT}" /proc/uptime)
		__start="${__start% *}"

		for _ in $("${SEQ}" 1 5); do
			"${CP}" "${compress_test1}" "${compress_test2}"
			${__cmd} --force -d -c "${compress_test2}" > /dev/null
		done

		__end=$("${CAT}" /proc/uptime)
		__end="${__end% *}"

		__time="$(echo "${__end} - ${__start}" | "${BC}" -l)"
		__size="$("${LS}" -s --block-size=1 "${compress_test1}")"
		__size="${__size%% *}"

		if [ -z "${__min_time}" ]; then
			__min_time="${__time}"
			__pick="${__a}"
		else
			__cmp="$(echo "${__time} < ${__min_time}" | "${BC}" -l)"
			if [ "${__cmp}" = "1" ]; then
				__min_time="${__time}"
				__pick="${__a}"
			fi
		fi

		notice "%5s %15s, 5 cycles in %7s" \
		       "${__a}:" "${__size} bytes" "${__time}s"
	done

	notice "Picked ${__pick} compression for CPIO"
	rm ${compress_test1} ${compress_test2}
	echo "${__pick}"
}

# cpio_compress() - Compress archive, test available methods if none is selected
# $1:	Existing CPIO archive
cpio_compress() {
	{ [ -z "${COMPRESS}" ] || [ "${COMPRESS}" = "none" ]; } && return
	[ "${COMPRESS}" = "auto" ] && COMPRESS=$(compress_select "$1")

	info "Compressing CPIO archive ${1}"

	[ "${COMPRESS}" = "lzo" ] && __cmd="lzop" || __cmd="${COMPRESS}"

	cmd_check "${__cmd}"
	if [ "${__cmd}" = "lz4" ]; then
		"${__cmd}" -l -f -q -9 "${1}" "${1}.lz4"
	else
		"${__cmd}" -f -q -9 -S .lz4 "${1}"
	fi

	mv "${1}.lz4" "${1}"
}

################################################################################


### Shared libraries ###########################################################

# libs_path_add() - Add library path to dynamic linker configuration
# $1:	Path to be added, skipped if already present
libs_path_add() {
	for __l in $("${CAT}" "${wd}/etc/ld.so.conf" 2>/dev/null); do
		[ "${__l}" = "${1}" ] && return
	done
	"${MKDIR}" -p "${wd}/etc"
	echo "${1}" >> "${wd}/etc/ld.so.conf"
	"${LDCONFIG}" -r "${wd}"
}

# libs_copy_ld_so() - Copy run-time linker program, mimic location from host
# $1:	Path to run-time linker
libs_copy_ld_so() {
	[ -f "${wd}/${1}" ] && return

	__destdir="$("${DIRNAME}" "${wd}/${1}")"
	"${MKDIR}" -p "${__destdir}"

	"${CP}" --parents --preserve=all "${1}" "${wd}"
}

# libs_dlopen_copy() - Recursively copy matching libraries from LIBS_DLOPEN
# $1:	Library (base path) that loads further libraries using dlopen(3)
libs_dlopen_copy() {
	__match=
	for __t in ${LIBS_DLOPEN}; do
		[ -z "${__match}" ] && __match="${__t}" && continue
		if [ "${__match}" = "$(basename ${1})" ]; then
			__path="$(dirname "${1}")/${__t}"
			"${CP}" --parents --preserve=all "${__path}" "${wd}"
			libs_copy "${__path}"
			__match=
		fi
	done
}

# __libs_copy() - Recursively copy shared dependencies for programs, libraries
# $1:	Host path to program or library
__libs_copy() {
	info "Sourcing shared object dependencies for ${1}"

	# ldd might succeed and print errors on stdout when paths are not found.
	# Skip those lines, as those paths are not actually needed on the host.
	for __l in $("${LDD}" "${1}" 2>/dev/null | "${GREP}" -v "ERROR:"); do
		__path="${__l##*=> }"
		[ "${__path}" = "not found" ] && continue
		[ "${__l}" = "${__path}" ] && continue
		__path="${__path%% *}"

		# fakeroot dependency from LD_PRELOAD will always appear, skip
		[ "$("${BASENAME}" "${__path}")" = "libfakeroot-sysv.so" ] && \
			continue

		__destpath="${wd}${__path}"
		[ -f "${__destpath}" ] && continue

		__destdir="$("${DIRNAME}" "${__destpath}")"
		"${MKDIR}" -p "${__destdir}"

		"${CP}" --parents --preserve=all "${__path}" "${wd}"
		libs_path_add "${__destdir##${wd}}"

		# Recurse over all shared object dependencies
		libs_copy "${__path}"
	done

	libs_dlopen_copy "${1}"

	# Dynamic linker is listed as single path, make sure we copy it once
	__ld_so=
	for __l in $("${LDD}" "${1}" 2>/dev/null); do
		case ${__l} in "/"*" "*) __ld_so="${__l% *}" ;; *) ;; esac
	done
	if [ -n "${__ld_so}" ]; then
		libs_copy_ld_so "${__ld_so}"
		libs_path_add "${__ld_so##${wd}}"
	fi
}

# libs_copy() - Call __libs_copy with tabs and newlines as IFS
# $@:	Arguments to __libs_copy
libs_copy() {
	IFS='	
'
	__libs_copy "${@}"
	unset IFS
}

################################################################################


### Programs ###################################################################

# prog_link() - Link from $LINKS to sourced program from $PROGS
# $1:	Program name from $PROGS
# $2:	Target on working directory $wd
# $3:	Even-numbered token from $LINKS, skip if not matching
# $4:	Link name, next token from $LINKS
prog_link() {
	[ "${1}" != "${3}" ] && return
	"${MKDIR}" -p "$("${DIRNAME}" "${wd}${4}")"
	"${LN}" -s "${2}" "${wd}${4}" 2>/dev/null || :
}

# prog_add() - Add a program from $PROGS
# $1:	Program name seen from shell
prog_add() {
	info "Adding program ${1}"

	IFS=','
	for __a in ${1}; do
		# We might have alias definitions too, look for a path
		IFS='
'
		__bin=
		for __path in $(${WHICH} -a "${__a}" 2>/dev/null); do
			[ -x "${__path}" ] && __bin="${__path}" && break
		done
		[ -n "${__bin}" ] && break
		IFS=','
	done
	unset IFS
	[ -z "${__bin}" ] && err "Can't source ${1}"

	# Binaries in /usr/libexec are meant to run on other hosts only, so they
	# can't reside in /usr/libexec on the target image. Move to /usr/bin.
	if [ "$("${DIRNAME}" "${__bin}")" = "/usr/libexec" ]; then
		__bindir="${wd}/usr/bin"
	else
		__bindir="${wd}$("${DIRNAME}" "${__bin}")"
	fi

	"${MKDIR}" -p "${__bindir}"
	"${CP}" --preserve=all "${__bin}" "${__bindir}"

	__target=
	for __l in ${LINKS}; do
		[ -z "${__target}" ] && __target="${__l}" && continue

		prog_link "${1}" \
			  "${__bindir##${wd}}/$("${BASENAME}" "${__bin}")" \
			  "${__target}" "${__l}"
		__target=
	done

	libs_copy "${__bin}"
}

################################################################################


### Kernel modules #############################################################

# kmod_init() - Set $KERNEL, create modules directory on working image $wd
kmod_init() {
	KERNEL="${KERNEL:-$("${UNAME}" -r)}"

	"${MKDIR}" -p "${wd}/lib/modules/${KERNEL}/kernel"
}

# __kmod_node() - Actually create device node for path found in modules.devname
# $1:	Module name, not used here, indicates grep matched in caller
# $2:	Name of device node
# $3:	Type and device major as a single token, from modules.devname
# $4:	Device minor
__kmod_node() {
	{ [ -z "${1}" ] || [ -e "${wd}/dev/${2}" ]; } && return

	"${MKDIR}" -p "$("${DIRNAME}" "${wd}/dev/${2}")"
	"${MKNOD}" "${wd}/dev/${2}" "${3%%[0-9]*}" "${3#[a-z]*}" "${4}"
}

# kmod_node() - Add device nodes for on-demand module loading
# $1:	Module name
kmod_node() {
	__devname="${MODDIR}/lib/modules/${KERNEL}/modules.devname"
	IFS=' :'
	__kmod_node $("${GREP}" "^${1} " "${__devname}")
	unset IFS
}

# kmod_add() - Add a kernel module to working directory
# $1:	Module name
kmod_add() {
	info "Adding kernel module ${1}"

	if [ -z "${MODDIR}" ]; then
		__d_opt="/"
		__find_path="/lib/modules/${KERNEL}"
	else
		__d_opt="${MODDIR}"
		__find_path="${MODDIR}/lib/modules"
	fi

	__f=$("${MODPROBE}" -S "${KERNEL}" -d "${__d_opt}" -q \
		--show-depends -- "$(${BASENAME} -- ${1})")

	case ${__f} in
	"builtin "*)
		kmod_node "${1}"
		;;
	*)
		for __t in ${__f}; do
			__t="$(${BASENAME} -- "${__t}")"
			__t="${__t%%.*}"
			if ${MODPROBE} -S "${KERNEL}" -d "${__d_opt}" -q \
				--show-depends -- "$(${BASENAME} -- "${__t}")" >/dev/null; then

				__src="$(${FIND} ${__find_path} -name "${__t}.*")"
				[ -z "${__src}" ] && continue

				__dst="${wd}${__src##${MODDIR}}"
				if ! "${DIFF}" "${__src}" "${__dst}" 2>/dev/null; then
					"${MKDIR}" -p "$("${DIRNAME}" "${__dst}")"
					"${CP}" -a "${__src}" "${__dst}"
				fi

				kmod_node "${__t}"
			fi
		done
	esac
}

# kmod_post() - Copy files for modprobe on target, run depmod
kmod_post() {
	"${CP}" -a "${MODDIR}/lib/modules/${KERNEL}/modules.order" \
		   "${MODDIR}/lib/modules/${KERNEL}/modules.builtin" \
		   "${wd}/lib/modules/${KERNEL}"
	"${DEPMOD}" -b "${wd}" "${KERNEL}"
}

# kmod_strip_worker() - Strip debug information from modules, call via workers()
# $1:	Worker thread number, used to select paths from $KMODS
kmod_strip_worker() {
	__i=1
	for __kmod in ${KMODS}; do
		[ ${__i} -eq ${1} ] && ${STRIP} --strip-debug ${__kmod}
		__i=$((__i + 1))
		[ ${__i} -eq ${THREADS} ] && __i=1
	done
	return 0
}

################################################################################


### Device nodes ###############################################################

# node_add() - Add device node from $NODES
node_add() {
	info "Adding device node ${1}"

	"${MKDIR}" -p "$("${DIRNAME}" "${wd}/dev/${1}")"
	if [ -n "${2}" ]; then
		"${MKNOD}" "${wd}/dev/${1}" "${2}" "${3}" "${4}"
	else
		"${CP}" -a "/dev/${1}" "${wd}/dev/"
	fi
}

################################################################################


### Packages ###################################################################

# pkg_deb_add() - Extract Debian package, source shared object dependencies
# $1:	Package file
pkg_deb_add() {
	cmd_check dpkg-deb

	info "Adding Debian package ${1}"

	for __f in $("${DPKG_DEB}" -X "${1}" "${wd}"); do
		__type="$("${FILE}" -bi "${wd}/${__f}")"
		if [ "${__type%%;*}" = "application/x-sharedlib" ] ||
		   [ "${__type%%;*}" = "application/x-executable" ]; then
			libs_copy "${wd}/${__f}"
		fi
	done
}

# pkg_deb_add() - Extract RPM package, source shared object dependencies
# $1:	Package file
pkg_rpm_add() {
	cmd_check rpm2cpio

	info "Adding RPM package ${1}"

	for __f in $("${RPM2CPIO}" "${1}" |
		     cpio -D "${wd}" --quiet -idvmu 2>&1 |
		     "${GREP}" -v "cpio:"); do
		__type="$("${FILE}" -bi "${wd}/${__f}")"
		if [ "${__type%%;*}" = "application/x-sharedlib" ] ||
		   [ "${__type%%;*}" = "application/x-executable" ]; then
			libs_copy "${wd}/${__f}"
		fi
	done
}

# pkg_add() - Source and extract a package into working directory
# $1:	Package file or URL
pkg_add() {
	info "Adding package ${1}"

	pkg_tmp="$("${MKTEMP}")"

	if [ ! -f "${1}" ]; then
		info "Downloading ${1}"
		"${WGET}" --quiet -O "${pkg_tmp}" "${1}" || \
			err "${1} not found"
		__file="${pkg_tmp}"
	else
		__file="${1}"
	fi

	case $("${FILE}" -bi "${__file}") in
	"application/vnd.debian.binary-package;"*)
		pkg_deb_add "${__file}"
		;;
	"application/x-rpm;"*)
		pkg_rpm_add "${__file}"
		;;
	*)
		err "package format not supported"
		;;
	esac

	rm "${pkg_tmp}"
}

################################################################################


### Commands and user interface ################################################

# build() - Build a new image, sourcing contents
build() {
	kmod_init

	for __d in ${DIRS}; do
		"${MKDIR}" -p "${wd}${__d}"
	done

	for __p in ${PROGS}; do
		PATH="${PATH}:/usr/libexec:/sbin:/usr/sbin" prog_add "${__p}"
	done

	for __k in ${KMODS}; do
		kmod_add "${__k}"
	done
	kmod_post

	for __n in ${NODES}; do
		IFS=','
		node_add ${__n}
		unset IFS
	done

	for __c in ${COPIES}; do
		set +f
		case ${__c} in
		*","*)	"${CP}" -a ${__c%,*} "${wd}${__c#*,}"	;;
		*)	"${CP}" --parent -a "${__c}" "${wd}"	;;
		esac
		set -f
	done

	for __p in ${PKGS}; do
		pkg_add "${__p}"
	done

	fixup_apply
}

# add() - Add contents to existing image
# $1:	Item to be added
add() {
	kmod_init

	[ -e "${1}" ] && __path="${1}" || __path=$(command -v "${1}") || :
	if [ ! -e "${__path}" ]; then
		{ pkg_add "${1}" && return; } || err "${1} not found"
	fi

	case $("${FILE}" -bi "${__path}") in
	"application/x-sharedlib;"* | "application/x-pie-executable;"*)
		PATH="${PATH}:/usr/libexec:/sbin:/usr/sbin" prog_add "${__path}"
		;;
	"application/x-object;"*)
		kmod_add "${__path}"
		kmod_post
		;;
	"application/vnd.debian.binary-package;"*)
		pkg_deb_add "${__path}"
		;;
	"application/x-rpm;"*)
		pkg_rpm_add "${__path}"
		;;
	*)
		"${CP}" --parents -a "$(${REALPATH} "${__path}")" "${wd}"
		;;
	esac
set +x
}

# stats() - Print size information about created image
stats() {
	__bin="$(dir_size bin usr/bin sbin usr/sbin usr/local/bin \
		 usr/local/sbin)"
	__lib="$(dir_size --exclude='lib/modules/*' lib lib64 usr/lib \
		 usr/lib32 usr/lib64 usr/local/lib)"
	__mod="$(dir_size lib/modules)"
	__sum="$(dir_size .)"

	__compressed="$("${DU}" --si "${OUT}")"
	__compressed="${__compressed%[ 	]*}"

	notice "Size: bin %5s lib %5s kmod %5s total %5s compressed %5s" \
	       "${__bin}" "${__lib}" "${__mod}" "${__sum}" "${__compressed}"
}

# cmds() - Loop over non-option arguments if any, just build image otherwise
# $@:	Command arguments
cmds() {
	{ [ ! -f "${OUT}" ] || [ -z "${1}" ]; } && build

	for arg do
		add "${arg}"
	done

	[ "${NOSTRIP}" != "y" ] && strip_all

	(
		"${CD}" "${wd}"
		"${FIND}" . | "${CPIO}" --create -H newc --quiet
	) > "${OUT}"
	cpio_compress "${OUT}"

	stats

	printf "%s" "${OUTPUT}"						| \
		sed 's,__INITRD__,'"$("${REALPATH}" "${OUT}")"',g'	| \
		sed 's,__KERNEL__,/boot/vmlinuz-'"${KERNEL}"',g'
}

# usage() - Print usage and exit
usage() {
	echo "${0} [OPTIONS] [ADD_ON]..."
	echo
	echo "Options:"
	echo "	-c gzip|lz4|lzma|lzo|auto|none"
	echo "		compression method for CPIO file. Default: none"
	echo "	-d"
	echo "		don't strip binary objects"
	echo "	-f PATH"
	echo "		path for initramfs. Default: temporary file"
	echo "	-k VERSION"
	echo "		kernel version. Default: $(uname -r)"
	echo "	-m PATH"
	echo "		relative root for /lib/modules. Default: /"
	echo "	-p PROFILE|FILE"
	echo "		select profile for add-ons, built-in PROFILEs are:"
	echo "			base bash kata kata_debug passt kselftests"
	echo
	echo "		alternatively, an executable shell script FILE"
	echo "		including assignments of configuration variables can be"
	echo "		used. To avoid collisions with built-in profiles, FILE"
	echo "		needs to be as a command, e.g. './passt', not 'passt'"
	echo
	echo "		Default: base"
	echo
	echo "		kselftests sub-options (same as run_kselftests.sh):"
	echo "			-L"
	echo "				list available collections"
	echo "			-C collection"
	echo "				select a collection of tests to run"
	echo "			-T collection:test"
	echo "				select a specific test to run"
	echo "		Examples:"
	echo "			-p kselftests -C net"
	echo "				run all the tests in net collection"
	echo "			-p kselftests -T net:ip_defrag.sh"
	echo "				run ip_defrag.sh from net collection"
	echo "	-s SCRIPT|-"
	echo "		fix-up script to run before init, '-' for none"
	echo "	-v: verbose"
	echo "	-h: show this help"
	echo
	echo "Build initramfs image.  Additional programs, kernel modules,"
	echo "device nodes, generic files can be passed as a list of ADD_ON"
	echo "parameters."
	echo
	echo "Distribution packages (deb and RPM currently supported) can be"
	echo "added too, but they will simply be extracted, not installed."
	echo "Package dependencies are also not sourced."
	echo
	echo "Environmental variables can be used to replace:"
	echo "	PROGS"
	echo "		base programs"
	echo "	LIBS_DLOPEN"
	echo "		additional libraries, not directly linked from"
	echo "		programs, typically loaded via dlopen(3) by matching"
	echo "		libraries, in the form"
	echo "			MATCHING_LIBRARY ADDITIONAL_LIBRARY"
	echo "	KMODS"
	echo "		base kernel modules"
	echo "	NODES"
	echo "		device nodes, copied from host if just name is given, "
	echo "		created if NAME,TYPE,MAJOR,MINOR"
	echo "	LINKS"
	echo "		link to programs by name, pairs of"
	echo "			PROGRAM PATH"
	echo "	DIRS"
	echo "		list of initial set of empty directories"
	echo "	COPIES"
	echo "		full copy of path, preserving attributes and parents"
	echo
	echo "Examples:"
	echo "	${0}"
	echo "		Build a base image as a temporary file"
	echo "	${0} grep"
	echo "		Build a new image including grep and needed libraries"
	echo "	${0} -v -f kata.img -p kata_debug -c lz4"
	echo "		Use lz4 compression, run a shell before proceeding"
	exit 1
}

################################################################################


# If we're not running as root, re-run with fakeroot
if [ "$(id -u)" -eq 0 ] && \
   [ "$(cat /proc/self/uid_map)" = "         0          0 4294967295" ]; then
	__as_root=1
else
	__as_root=0
fi

if [ "${LD_PRELOAD}" != "libfakeroot-sysv.so" ] && [ ${__as_root} -eq 0 ]; then
	if ! FAKEROOT="$(command -v fakeroot)"; then
		__fakeroot_missing="y"
	else
		PATH="${PATH}:/usr/local/sbin:/sbin:/usr/sbin" \
			"${FAKEROOT}" "${0}" "${@}"
		exit ${?}
	fi
fi

# Check needed tools, exit if any is missing
for __l in ${TOOLS}; do
	cmd_check "${__l}"
done

# Parse options
while getopts :c:df:k:m:p:s:vh __opt; do
	case ${__opt} in
	c) COMPRESS="${OPTARG}"					;;
	d) NOSTRIP="y"						;;
	f) OUT="${OPTARG}"					;;
	k) KERNEL="${OPTARG}"					;;
	m) MODDIR="${OPTARG}"					;;
	p) PROFILE="${OPTARG}"					;;
	s) SCRIPT="${OPTARG}"					;;
	v) VERBOSE="y"						;;
	?)
		eval arg=\${$((OPTIND))}
		subopts_get "${OPTARG}" "${arg}" || __subopt_args=$?
		OPTIND=$((OPTIND + ${__subopt_args} - 1))
		;;
	h|*) usage						;;
	esac
done
shift $((OPTIND - 1))
[ -z "${PROFILE}" ] && PROFILE="base"

if [ "${__fakeroot_missing}" = "y" ]; then
	err "Not root and no fakeroot available, exiting"
	exit 1
fi

# Create working directory
wd="$("${MKTEMP}" -d)"

# Apply profile requested from file or matching profile_*() function
if [ -x "${PROFILE}" ] && __profile_file="$(command -v ${PROFILE})" && \
   [ "${PROFILE}" = "${__profile_file}" ]; then
	notice "Applying profile from file ${PROFILE}"
	. "${PROFILE}"
else
	notice "Applying built-in profile ${PROFILE}"
	eval "profile_${PROFILE}" || err "profile ${PROFILE} not found"
fi

trap cleanup EXIT

cmds "$@"

exit 0