Loading...
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4set -e
5
6# This script currently only works for the following platforms,
7# as it is based on the VM image used by the BPF CI, which is
8# available only for these architectures. We can also specify
9# the local rootfs image generated by the following script:
10# https://github.com/libbpf/ci/blob/main/rootfs/mkrootfs_debian.sh
11PLATFORM="${PLATFORM:-$(uname -m)}"
12case "${PLATFORM}" in
13s390x)
14 QEMU_BINARY=qemu-system-s390x
15 QEMU_CONSOLE="ttyS1"
16 HOST_FLAGS=(-smp 2 -enable-kvm)
17 CROSS_FLAGS=(-smp 2)
18 BZIMAGE="arch/s390/boot/vmlinux"
19 ARCH="s390"
20 ;;
21x86_64)
22 QEMU_BINARY=qemu-system-x86_64
23 QEMU_CONSOLE="ttyS0,115200"
24 HOST_FLAGS=(-cpu host -enable-kvm -smp 8)
25 CROSS_FLAGS=(-smp 8)
26 BZIMAGE="arch/x86/boot/bzImage"
27 ARCH="x86"
28 ;;
29aarch64)
30 QEMU_BINARY=qemu-system-aarch64
31 QEMU_CONSOLE="ttyAMA0,115200"
32 HOST_FLAGS=(-M virt,gic-version=3 -cpu host -enable-kvm -smp 8)
33 CROSS_FLAGS=(-M virt,gic-version=3 -cpu cortex-a76 -smp 8)
34 BZIMAGE="arch/arm64/boot/Image"
35 ARCH="arm64"
36 ;;
37riscv64)
38 # required qemu version v7.2.0+
39 QEMU_BINARY=qemu-system-riscv64
40 QEMU_CONSOLE="ttyS0,115200"
41 HOST_FLAGS=(-M virt -cpu host -enable-kvm -smp 8)
42 CROSS_FLAGS=(-M virt -cpu rv64,sscofpmf=true -smp 8)
43 BZIMAGE="arch/riscv/boot/Image"
44 ARCH="riscv"
45 ;;
46*)
47 echo "Unsupported architecture"
48 exit 1
49 ;;
50esac
51DEFAULT_COMMAND="./test_progs"
52MOUNT_DIR="mnt"
53LOCAL_ROOTFS_IMAGE=""
54ROOTFS_IMAGE="root.img"
55OUTPUT_DIR="$HOME/.bpf_selftests"
56KCONFIG_REL_PATHS=("tools/testing/selftests/bpf/config"
57 "tools/testing/selftests/bpf/config.vm"
58 "tools/testing/selftests/bpf/config.${PLATFORM}")
59INDEX_URL="https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
60NUM_COMPILE_JOBS="$(nproc)"
61LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")"
62LOG_FILE="${LOG_FILE_BASE}.log"
63EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
64
65usage()
66{
67 cat <<EOF
68Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
69
70<command> is the command you would normally run when you are in
71tools/testing/selftests/bpf. e.g:
72
73 $0 -- ./test_progs -t test_lsm
74
75If no command is specified and a debug shell (-s) is not requested,
76"${DEFAULT_COMMAND}" will be run by default.
77
78Using PLATFORM= and CROSS_COMPILE= options will enable cross platform testing:
79
80 PLATFORM=<platform> CROSS_COMPILE=<toolchain> $0 -- ./test_progs -t test_lsm
81
82If you build your kernel using KBUILD_OUTPUT= or O= options, these
83can be passed as environment variables to the script:
84
85 O=<kernel_build_path> $0 -- ./test_progs -t test_lsm
86
87or
88
89 KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm
90
91Options:
92
93 -l) Specify the path to the local rootfs image.
94 -i) Update the rootfs image with a newer version.
95 -d) Update the output directory (default: ${OUTPUT_DIR})
96 -j) Number of jobs for compilation, similar to -j in make
97 (default: ${NUM_COMPILE_JOBS})
98 -s) Instead of powering off the VM, start an interactive
99 shell. If <command> is specified, the shell runs after
100 the command finishes executing
101EOF
102}
103
104unset URLS
105populate_url_map()
106{
107 if ! declare -p URLS &> /dev/null; then
108 # URLS contain the mapping from file names to URLs where
109 # those files can be downloaded from.
110 declare -gA URLS
111 while IFS=$'\t' read -r name url; do
112 URLS["$name"]="$url"
113 done < <(curl -Lsf ${INDEX_URL})
114 fi
115}
116
117newest_rootfs_version()
118{
119 {
120 for file in "${!URLS[@]}"; do
121 if [[ $file =~ ^"${PLATFORM}"/libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then
122 echo "${BASH_REMATCH[1]}"
123 fi
124 done
125 } | sort -rV | head -1
126}
127
128download_rootfs()
129{
130 populate_url_map
131
132 local rootfsversion="$(newest_rootfs_version)"
133 local file="${PLATFORM}/libbpf-vmtest-rootfs-$rootfsversion.tar.zst"
134
135 if [[ ! -v URLS[$file] ]]; then
136 echo "$file not found" >&2
137 return 1
138 fi
139
140 echo "Downloading $file..." >&2
141 curl -Lsf "${URLS[$file]}" "${@:2}"
142}
143
144load_rootfs()
145{
146 local dir="$1"
147
148 if ! which zstd &> /dev/null; then
149 echo 'Could not find "zstd" on the system, please install zstd'
150 exit 1
151 fi
152
153 if [[ -n "${LOCAL_ROOTFS_IMAGE}" ]]; then
154 cat "${LOCAL_ROOTFS_IMAGE}" | zstd -d | sudo tar -C "$dir" -x
155 else
156 download_rootfs | zstd -d | sudo tar -C "$dir" -x
157 fi
158}
159
160recompile_kernel()
161{
162 local kernel_checkout="$1"
163 local make_command="$2"
164
165 cd "${kernel_checkout}"
166
167 ${make_command} olddefconfig
168 ${make_command}
169}
170
171mount_image()
172{
173 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
174 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
175
176 sudo mount -o loop "${rootfs_img}" "${mount_dir}"
177}
178
179unmount_image()
180{
181 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
182
183 sudo umount "${mount_dir}" &> /dev/null
184}
185
186update_selftests()
187{
188 local kernel_checkout="$1"
189 local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf"
190
191 cd "${selftests_dir}"
192 ${make_command}
193
194 # Mount the image and copy the selftests to the image.
195 mount_image
196 sudo rm -rf "${mount_dir}/root/bpf"
197 sudo cp -r "${selftests_dir}" "${mount_dir}/root"
198 unmount_image
199}
200
201update_init_script()
202{
203 local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d"
204 local init_script="${init_script_dir}/S50-startup"
205 local command="$1"
206 local exit_command="$2"
207
208 mount_image
209
210 if [[ ! -d "${init_script_dir}" ]]; then
211 cat <<EOF
212Could not find ${init_script_dir} in the mounted image.
213This likely indicates a bad rootfs image, Please download
214a new image by passing "-i" to the script
215EOF
216 exit 1
217
218 fi
219
220 sudo bash -c "echo '#!/bin/bash' > ${init_script}"
221
222 if [[ "${command}" != "" ]]; then
223 sudo bash -c "cat >>${init_script}" <<EOF
224# Have a default value in the exit status file
225# incase the VM is forcefully stopped.
226echo "130" > "/root/${EXIT_STATUS_FILE}"
227
228{
229 cd /root/bpf
230 echo ${command}
231 stdbuf -oL -eL ${command}
232 echo "\$?" > "/root/${EXIT_STATUS_FILE}"
233} 2>&1 | tee "/root/${LOG_FILE}"
234# Ensure that the logs are written to disk
235sync
236EOF
237 fi
238
239 sudo bash -c "echo ${exit_command} >> ${init_script}"
240 sudo chmod a+x "${init_script}"
241 unmount_image
242}
243
244create_vm_image()
245{
246 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
247 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
248
249 rm -rf "${rootfs_img}"
250 touch "${rootfs_img}"
251 chattr +C "${rootfs_img}" >/dev/null 2>&1 || true
252
253 truncate -s 2G "${rootfs_img}"
254 mkfs.ext4 -q "${rootfs_img}"
255
256 mount_image
257 load_rootfs "${mount_dir}"
258 unmount_image
259}
260
261run_vm()
262{
263 local kernel_bzimage="$1"
264 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
265
266 if ! which "${QEMU_BINARY}" &> /dev/null; then
267 cat <<EOF
268Could not find ${QEMU_BINARY}
269Please install qemu or set the QEMU_BINARY environment variable.
270EOF
271 exit 1
272 fi
273
274 if [[ "${PLATFORM}" != "$(uname -m)" ]]; then
275 QEMU_FLAGS=("${CROSS_FLAGS[@]}")
276 else
277 QEMU_FLAGS=("${HOST_FLAGS[@]}")
278 fi
279
280 ${QEMU_BINARY} \
281 -nodefaults \
282 -display none \
283 -serial mon:stdio \
284 "${QEMU_FLAGS[@]}" \
285 -m 4G \
286 -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
287 -kernel "${kernel_bzimage}" \
288 -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
289}
290
291copy_logs()
292{
293 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
294 local log_file="${mount_dir}/root/${LOG_FILE}"
295 local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}"
296
297 mount_image
298 sudo cp ${log_file} "${OUTPUT_DIR}"
299 sudo cp ${exit_status_file} "${OUTPUT_DIR}"
300 sudo rm -f ${log_file}
301 unmount_image
302}
303
304is_rel_path()
305{
306 local path="$1"
307
308 [[ ${path:0:1} != "/" ]]
309}
310
311do_update_kconfig()
312{
313 local kernel_checkout="$1"
314 local kconfig_file="$2"
315
316 rm -f "$kconfig_file" 2> /dev/null
317
318 for config in "${KCONFIG_REL_PATHS[@]}"; do
319 local kconfig_src="${kernel_checkout}/${config}"
320 cat "$kconfig_src" >> "$kconfig_file"
321 done
322}
323
324update_kconfig()
325{
326 local kernel_checkout="$1"
327 local kconfig_file="$2"
328
329 if [[ -f "${kconfig_file}" ]]; then
330 local local_modified="$(stat -c %Y "${kconfig_file}")"
331
332 for config in "${KCONFIG_REL_PATHS[@]}"; do
333 local kconfig_src="${kernel_checkout}/${config}"
334 local src_modified="$(stat -c %Y "${kconfig_src}")"
335 # Only update the config if it has been updated after the
336 # previously cached config was created. This avoids
337 # unnecessarily compiling the kernel and selftests.
338 if [[ "${src_modified}" -gt "${local_modified}" ]]; then
339 do_update_kconfig "$kernel_checkout" "$kconfig_file"
340 # Once we have found one outdated configuration
341 # there is no need to check other ones.
342 break
343 fi
344 done
345 else
346 do_update_kconfig "$kernel_checkout" "$kconfig_file"
347 fi
348}
349
350catch()
351{
352 local exit_code=$1
353 local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}"
354 # This is just a cleanup and the directory may
355 # have already been unmounted. So, don't let this
356 # clobber the error code we intend to return.
357 unmount_image || true
358 if [[ -f "${exit_status_file}" ]]; then
359 exit_code="$(cat ${exit_status_file})"
360 fi
361 exit ${exit_code}
362}
363
364main()
365{
366 local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
367 local kernel_checkout=$(realpath "${script_dir}"/../../../../)
368 # By default the script searches for the kernel in the checkout directory but
369 # it also obeys environment variables O= and KBUILD_OUTPUT=
370 local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
371 local command="${DEFAULT_COMMAND}"
372 local update_image="no"
373 local exit_command="poweroff -f"
374 local debug_shell="no"
375
376 while getopts ':hskl:id:j:' opt; do
377 case ${opt} in
378 l)
379 LOCAL_ROOTFS_IMAGE="$OPTARG"
380 ;;
381 i)
382 update_image="yes"
383 ;;
384 d)
385 OUTPUT_DIR="$OPTARG"
386 ;;
387 j)
388 NUM_COMPILE_JOBS="$OPTARG"
389 ;;
390 s)
391 command=""
392 debug_shell="yes"
393 exit_command="bash"
394 ;;
395 h)
396 usage
397 exit 0
398 ;;
399 \? )
400 echo "Invalid Option: -$OPTARG"
401 usage
402 exit 1
403 ;;
404 : )
405 echo "Invalid Option: -$OPTARG requires an argument"
406 usage
407 exit 1
408 ;;
409 esac
410 done
411 shift $((OPTIND -1))
412
413 trap 'catch "$?"' EXIT
414
415 if [[ "${PLATFORM}" != "$(uname -m)" ]] && [[ -z "${CROSS_COMPILE}" ]]; then
416 echo "Cross-platform testing needs to specify CROSS_COMPILE"
417 exit 1
418 fi
419
420 if [[ $# -eq 0 && "${debug_shell}" == "no" ]]; then
421 echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
422 else
423 command="$@"
424 fi
425
426 local kconfig_file="${OUTPUT_DIR}/latest.config"
427 local make_command="make ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} \
428 -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
429
430 # Figure out where the kernel is being built.
431 # O takes precedence over KBUILD_OUTPUT.
432 if [[ "${O:=""}" != "" ]]; then
433 if is_rel_path "${O}"; then
434 O="$(realpath "${PWD}/${O}")"
435 fi
436 kernel_bzimage="${O}/${BZIMAGE}"
437 make_command="${make_command} O=${O}"
438 elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
439 if is_rel_path "${KBUILD_OUTPUT}"; then
440 KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
441 fi
442 kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
443 make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
444 fi
445
446 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
447 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
448
449 echo "Output directory: ${OUTPUT_DIR}"
450
451 mkdir -p "${OUTPUT_DIR}"
452 mkdir -p "${mount_dir}"
453 update_kconfig "${kernel_checkout}" "${kconfig_file}"
454
455 recompile_kernel "${kernel_checkout}" "${make_command}"
456
457 if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then
458 echo "rootfs image not found in ${rootfs_img}"
459 update_image="yes"
460 fi
461
462 if [[ "${update_image}" == "yes" ]]; then
463 create_vm_image
464 fi
465
466 update_selftests "${kernel_checkout}" "${make_command}"
467 update_init_script "${command}" "${exit_command}"
468 run_vm "${kernel_bzimage}"
469 if [[ "${command}" != "" ]]; then
470 copy_logs
471 echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
472 fi
473}
474
475main "$@"
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4set -u
5set -e
6
7# This script currently only works for x86_64 and s390x, as
8# it is based on the VM image used by the BPF CI, which is
9# available only for these architectures.
10ARCH="$(uname -m)"
11case "${ARCH}" in
12s390x)
13 QEMU_BINARY=qemu-system-s390x
14 QEMU_CONSOLE="ttyS1"
15 QEMU_FLAGS=(-smp 2)
16 BZIMAGE="arch/s390/boot/compressed/vmlinux"
17 ;;
18x86_64)
19 QEMU_BINARY=qemu-system-x86_64
20 QEMU_CONSOLE="ttyS0,115200"
21 QEMU_FLAGS=(-cpu host -smp 8)
22 BZIMAGE="arch/x86/boot/bzImage"
23 ;;
24aarch64)
25 QEMU_BINARY=qemu-system-aarch64
26 QEMU_CONSOLE="ttyAMA0,115200"
27 QEMU_FLAGS=(-M virt,gic-version=3 -cpu host -smp 8)
28 BZIMAGE="arch/arm64/boot/Image"
29 ;;
30*)
31 echo "Unsupported architecture"
32 exit 1
33 ;;
34esac
35DEFAULT_COMMAND="./test_progs"
36MOUNT_DIR="mnt"
37ROOTFS_IMAGE="root.img"
38OUTPUT_DIR="$HOME/.bpf_selftests"
39KCONFIG_REL_PATHS=("tools/testing/selftests/bpf/config" "tools/testing/selftests/bpf/config.${ARCH}")
40INDEX_URL="https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
41NUM_COMPILE_JOBS="$(nproc)"
42LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")"
43LOG_FILE="${LOG_FILE_BASE}.log"
44EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
45
46usage()
47{
48 cat <<EOF
49Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
50
51<command> is the command you would normally run when you are in
52tools/testing/selftests/bpf. e.g:
53
54 $0 -- ./test_progs -t test_lsm
55
56If no command is specified and a debug shell (-s) is not requested,
57"${DEFAULT_COMMAND}" will be run by default.
58
59If you build your kernel using KBUILD_OUTPUT= or O= options, these
60can be passed as environment variables to the script:
61
62 O=<kernel_build_path> $0 -- ./test_progs -t test_lsm
63
64or
65
66 KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm
67
68Options:
69
70 -i) Update the rootfs image with a newer version.
71 -d) Update the output directory (default: ${OUTPUT_DIR})
72 -j) Number of jobs for compilation, similar to -j in make
73 (default: ${NUM_COMPILE_JOBS})
74 -s) Instead of powering off the VM, start an interactive
75 shell. If <command> is specified, the shell runs after
76 the command finishes executing
77EOF
78}
79
80unset URLS
81populate_url_map()
82{
83 if ! declare -p URLS &> /dev/null; then
84 # URLS contain the mapping from file names to URLs where
85 # those files can be downloaded from.
86 declare -gA URLS
87 while IFS=$'\t' read -r name url; do
88 URLS["$name"]="$url"
89 done < <(curl -Lsf ${INDEX_URL})
90 fi
91}
92
93download()
94{
95 local file="$1"
96
97 if [[ ! -v URLS[$file] ]]; then
98 echo "$file not found" >&2
99 return 1
100 fi
101
102 echo "Downloading $file..." >&2
103 curl -Lsf "${URLS[$file]}" "${@:2}"
104}
105
106newest_rootfs_version()
107{
108 {
109 for file in "${!URLS[@]}"; do
110 if [[ $file =~ ^"${ARCH}"/libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then
111 echo "${BASH_REMATCH[1]}"
112 fi
113 done
114 } | sort -rV | head -1
115}
116
117download_rootfs()
118{
119 local rootfsversion="$1"
120 local dir="$2"
121
122 if ! which zstd &> /dev/null; then
123 echo 'Could not find "zstd" on the system, please install zstd'
124 exit 1
125 fi
126
127 download "${ARCH}/libbpf-vmtest-rootfs-$rootfsversion.tar.zst" |
128 zstd -d | sudo tar -C "$dir" -x
129}
130
131recompile_kernel()
132{
133 local kernel_checkout="$1"
134 local make_command="$2"
135
136 cd "${kernel_checkout}"
137
138 ${make_command} olddefconfig
139 ${make_command}
140}
141
142mount_image()
143{
144 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
145 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
146
147 sudo mount -o loop "${rootfs_img}" "${mount_dir}"
148}
149
150unmount_image()
151{
152 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
153
154 sudo umount "${mount_dir}" &> /dev/null
155}
156
157update_selftests()
158{
159 local kernel_checkout="$1"
160 local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf"
161
162 cd "${selftests_dir}"
163 ${make_command}
164
165 # Mount the image and copy the selftests to the image.
166 mount_image
167 sudo rm -rf "${mount_dir}/root/bpf"
168 sudo cp -r "${selftests_dir}" "${mount_dir}/root"
169 unmount_image
170}
171
172update_init_script()
173{
174 local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d"
175 local init_script="${init_script_dir}/S50-startup"
176 local command="$1"
177 local exit_command="$2"
178
179 mount_image
180
181 if [[ ! -d "${init_script_dir}" ]]; then
182 cat <<EOF
183Could not find ${init_script_dir} in the mounted image.
184This likely indicates a bad rootfs image, Please download
185a new image by passing "-i" to the script
186EOF
187 exit 1
188
189 fi
190
191 sudo bash -c "echo '#!/bin/bash' > ${init_script}"
192
193 if [[ "${command}" != "" ]]; then
194 sudo bash -c "cat >>${init_script}" <<EOF
195# Have a default value in the exit status file
196# incase the VM is forcefully stopped.
197echo "130" > "/root/${EXIT_STATUS_FILE}"
198
199{
200 cd /root/bpf
201 echo ${command}
202 stdbuf -oL -eL ${command}
203 echo "\$?" > "/root/${EXIT_STATUS_FILE}"
204} 2>&1 | tee "/root/${LOG_FILE}"
205# Ensure that the logs are written to disk
206sync
207EOF
208 fi
209
210 sudo bash -c "echo ${exit_command} >> ${init_script}"
211 sudo chmod a+x "${init_script}"
212 unmount_image
213}
214
215create_vm_image()
216{
217 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
218 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
219
220 rm -rf "${rootfs_img}"
221 touch "${rootfs_img}"
222 chattr +C "${rootfs_img}" >/dev/null 2>&1 || true
223
224 truncate -s 2G "${rootfs_img}"
225 mkfs.ext4 -q "${rootfs_img}"
226
227 mount_image
228 download_rootfs "$(newest_rootfs_version)" "${mount_dir}"
229 unmount_image
230}
231
232run_vm()
233{
234 local kernel_bzimage="$1"
235 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
236
237 if ! which "${QEMU_BINARY}" &> /dev/null; then
238 cat <<EOF
239Could not find ${QEMU_BINARY}
240Please install qemu or set the QEMU_BINARY environment variable.
241EOF
242 exit 1
243 fi
244
245 ${QEMU_BINARY} \
246 -nodefaults \
247 -display none \
248 -serial mon:stdio \
249 "${QEMU_FLAGS[@]}" \
250 -enable-kvm \
251 -m 4G \
252 -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
253 -kernel "${kernel_bzimage}" \
254 -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
255}
256
257copy_logs()
258{
259 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
260 local log_file="${mount_dir}/root/${LOG_FILE}"
261 local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}"
262
263 mount_image
264 sudo cp ${log_file} "${OUTPUT_DIR}"
265 sudo cp ${exit_status_file} "${OUTPUT_DIR}"
266 sudo rm -f ${log_file}
267 unmount_image
268}
269
270is_rel_path()
271{
272 local path="$1"
273
274 [[ ${path:0:1} != "/" ]]
275}
276
277do_update_kconfig()
278{
279 local kernel_checkout="$1"
280 local kconfig_file="$2"
281
282 rm -f "$kconfig_file" 2> /dev/null
283
284 for config in "${KCONFIG_REL_PATHS[@]}"; do
285 local kconfig_src="${kernel_checkout}/${config}"
286 cat "$kconfig_src" >> "$kconfig_file"
287 done
288}
289
290update_kconfig()
291{
292 local kernel_checkout="$1"
293 local kconfig_file="$2"
294
295 if [[ -f "${kconfig_file}" ]]; then
296 local local_modified="$(stat -c %Y "${kconfig_file}")"
297
298 for config in "${KCONFIG_REL_PATHS[@]}"; do
299 local kconfig_src="${kernel_checkout}/${config}"
300 local src_modified="$(stat -c %Y "${kconfig_src}")"
301 # Only update the config if it has been updated after the
302 # previously cached config was created. This avoids
303 # unnecessarily compiling the kernel and selftests.
304 if [[ "${src_modified}" -gt "${local_modified}" ]]; then
305 do_update_kconfig "$kernel_checkout" "$kconfig_file"
306 # Once we have found one outdated configuration
307 # there is no need to check other ones.
308 break
309 fi
310 done
311 else
312 do_update_kconfig "$kernel_checkout" "$kconfig_file"
313 fi
314}
315
316catch()
317{
318 local exit_code=$1
319 local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}"
320 # This is just a cleanup and the directory may
321 # have already been unmounted. So, don't let this
322 # clobber the error code we intend to return.
323 unmount_image || true
324 if [[ -f "${exit_status_file}" ]]; then
325 exit_code="$(cat ${exit_status_file})"
326 fi
327 exit ${exit_code}
328}
329
330main()
331{
332 local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
333 local kernel_checkout=$(realpath "${script_dir}"/../../../../)
334 # By default the script searches for the kernel in the checkout directory but
335 # it also obeys environment variables O= and KBUILD_OUTPUT=
336 local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
337 local command="${DEFAULT_COMMAND}"
338 local update_image="no"
339 local exit_command="poweroff -f"
340 local debug_shell="no"
341
342 while getopts ':hskid:j:' opt; do
343 case ${opt} in
344 i)
345 update_image="yes"
346 ;;
347 d)
348 OUTPUT_DIR="$OPTARG"
349 ;;
350 j)
351 NUM_COMPILE_JOBS="$OPTARG"
352 ;;
353 s)
354 command=""
355 debug_shell="yes"
356 exit_command="bash"
357 ;;
358 h)
359 usage
360 exit 0
361 ;;
362 \? )
363 echo "Invalid Option: -$OPTARG"
364 usage
365 exit 1
366 ;;
367 : )
368 echo "Invalid Option: -$OPTARG requires an argument"
369 usage
370 exit 1
371 ;;
372 esac
373 done
374 shift $((OPTIND -1))
375
376 trap 'catch "$?"' EXIT
377
378 if [[ $# -eq 0 && "${debug_shell}" == "no" ]]; then
379 echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
380 else
381 command="$@"
382 fi
383
384 local kconfig_file="${OUTPUT_DIR}/latest.config"
385 local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
386
387 # Figure out where the kernel is being built.
388 # O takes precedence over KBUILD_OUTPUT.
389 if [[ "${O:=""}" != "" ]]; then
390 if is_rel_path "${O}"; then
391 O="$(realpath "${PWD}/${O}")"
392 fi
393 kernel_bzimage="${O}/${BZIMAGE}"
394 make_command="${make_command} O=${O}"
395 elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
396 if is_rel_path "${KBUILD_OUTPUT}"; then
397 KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
398 fi
399 kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
400 make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
401 fi
402
403 populate_url_map
404
405 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
406 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
407
408 echo "Output directory: ${OUTPUT_DIR}"
409
410 mkdir -p "${OUTPUT_DIR}"
411 mkdir -p "${mount_dir}"
412 update_kconfig "${kernel_checkout}" "${kconfig_file}"
413
414 recompile_kernel "${kernel_checkout}" "${make_command}"
415
416 if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then
417 echo "rootfs image not found in ${rootfs_img}"
418 update_image="yes"
419 fi
420
421 if [[ "${update_image}" == "yes" ]]; then
422 create_vm_image
423 fi
424
425 update_selftests "${kernel_checkout}" "${make_command}"
426 update_init_script "${command}" "${exit_command}"
427 run_vm "${kernel_bzimage}"
428 if [[ "${command}" != "" ]]; then
429 copy_logs
430 echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
431 fi
432}
433
434main "$@"