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
|