aboutgitcode
path: root/mbuto
blob: 247b63de46fd73170be5be0d7b96f3959294110b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
#!/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