Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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 ;;
24*)
25 echo "Unsupported architecture"
26 exit 1
27 ;;
28esac
29DEFAULT_COMMAND="./test_progs"
30MOUNT_DIR="mnt"
31ROOTFS_IMAGE="root.img"
32OUTPUT_DIR="$HOME/.bpf_selftests"
33KCONFIG_URL="https://raw.githubusercontent.com/libbpf/libbpf/master/travis-ci/vmtest/configs/config-latest.${ARCH}"
34KCONFIG_API_URL="https://api.github.com/repos/libbpf/libbpf/contents/travis-ci/vmtest/configs/config-latest.${ARCH}"
35INDEX_URL="https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
36NUM_COMPILE_JOBS="$(nproc)"
37LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")"
38LOG_FILE="${LOG_FILE_BASE}.log"
39EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
40
41usage()
42{
43 cat <<EOF
44Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
45
46<command> is the command you would normally run when you are in
47tools/testing/selftests/bpf. e.g:
48
49 $0 -- ./test_progs -t test_lsm
50
51If no command is specified and a debug shell (-s) is not requested,
52"${DEFAULT_COMMAND}" will be run by default.
53
54If you build your kernel using KBUILD_OUTPUT= or O= options, these
55can be passed as environment variables to the script:
56
57 O=<kernel_build_path> $0 -- ./test_progs -t test_lsm
58
59or
60
61 KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm
62
63Options:
64
65 -i) Update the rootfs image with a newer version.
66 -d) Update the output directory (default: ${OUTPUT_DIR})
67 -j) Number of jobs for compilation, similar to -j in make
68 (default: ${NUM_COMPILE_JOBS})
69 -s) Instead of powering off the VM, start an interactive
70 shell. If <command> is specified, the shell runs after
71 the command finishes executing
72EOF
73}
74
75unset URLS
76populate_url_map()
77{
78 if ! declare -p URLS &> /dev/null; then
79 # URLS contain the mapping from file names to URLs where
80 # those files can be downloaded from.
81 declare -gA URLS
82 while IFS=$'\t' read -r name url; do
83 URLS["$name"]="$url"
84 done < <(curl -Lsf ${INDEX_URL})
85 fi
86}
87
88download()
89{
90 local file="$1"
91
92 if [[ ! -v URLS[$file] ]]; then
93 echo "$file not found" >&2
94 return 1
95 fi
96
97 echo "Downloading $file..." >&2
98 curl -Lsf "${URLS[$file]}" "${@:2}"
99}
100
101newest_rootfs_version()
102{
103 {
104 for file in "${!URLS[@]}"; do
105 if [[ $file =~ ^"${ARCH}"/libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then
106 echo "${BASH_REMATCH[1]}"
107 fi
108 done
109 } | sort -rV | head -1
110}
111
112download_rootfs()
113{
114 local rootfsversion="$1"
115 local dir="$2"
116
117 if ! which zstd &> /dev/null; then
118 echo 'Could not find "zstd" on the system, please install zstd'
119 exit 1
120 fi
121
122 download "${ARCH}/libbpf-vmtest-rootfs-$rootfsversion.tar.zst" |
123 zstd -d | sudo tar -C "$dir" -x
124}
125
126recompile_kernel()
127{
128 local kernel_checkout="$1"
129 local make_command="$2"
130
131 cd "${kernel_checkout}"
132
133 ${make_command} olddefconfig
134 ${make_command}
135}
136
137mount_image()
138{
139 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
140 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
141
142 sudo mount -o loop "${rootfs_img}" "${mount_dir}"
143}
144
145unmount_image()
146{
147 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
148
149 sudo umount "${mount_dir}" &> /dev/null
150}
151
152update_selftests()
153{
154 local kernel_checkout="$1"
155 local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf"
156
157 cd "${selftests_dir}"
158 ${make_command}
159
160 # Mount the image and copy the selftests to the image.
161 mount_image
162 sudo rm -rf "${mount_dir}/root/bpf"
163 sudo cp -r "${selftests_dir}" "${mount_dir}/root"
164 unmount_image
165}
166
167update_init_script()
168{
169 local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d"
170 local init_script="${init_script_dir}/S50-startup"
171 local command="$1"
172 local exit_command="$2"
173
174 mount_image
175
176 if [[ ! -d "${init_script_dir}" ]]; then
177 cat <<EOF
178Could not find ${init_script_dir} in the mounted image.
179This likely indicates a bad rootfs image, Please download
180a new image by passing "-i" to the script
181EOF
182 exit 1
183
184 fi
185
186 sudo bash -c "echo '#!/bin/bash' > ${init_script}"
187
188 if [[ "${command}" != "" ]]; then
189 sudo bash -c "cat >>${init_script}" <<EOF
190# Have a default value in the exit status file
191# incase the VM is forcefully stopped.
192echo "130" > "/root/${EXIT_STATUS_FILE}"
193
194{
195 cd /root/bpf
196 echo ${command}
197 stdbuf -oL -eL ${command}
198 echo "\$?" > "/root/${EXIT_STATUS_FILE}"
199} 2>&1 | tee "/root/${LOG_FILE}"
200# Ensure that the logs are written to disk
201sync
202EOF
203 fi
204
205 sudo bash -c "echo ${exit_command} >> ${init_script}"
206 sudo chmod a+x "${init_script}"
207 unmount_image
208}
209
210create_vm_image()
211{
212 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
213 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
214
215 rm -rf "${rootfs_img}"
216 touch "${rootfs_img}"
217 chattr +C "${rootfs_img}" >/dev/null 2>&1 || true
218
219 truncate -s 2G "${rootfs_img}"
220 mkfs.ext4 -q "${rootfs_img}"
221
222 mount_image
223 download_rootfs "$(newest_rootfs_version)" "${mount_dir}"
224 unmount_image
225}
226
227run_vm()
228{
229 local kernel_bzimage="$1"
230 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
231
232 if ! which "${QEMU_BINARY}" &> /dev/null; then
233 cat <<EOF
234Could not find ${QEMU_BINARY}
235Please install qemu or set the QEMU_BINARY environment variable.
236EOF
237 exit 1
238 fi
239
240 ${QEMU_BINARY} \
241 -nodefaults \
242 -display none \
243 -serial mon:stdio \
244 "${QEMU_FLAGS[@]}" \
245 -enable-kvm \
246 -m 4G \
247 -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
248 -kernel "${kernel_bzimage}" \
249 -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
250}
251
252copy_logs()
253{
254 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
255 local log_file="${mount_dir}/root/${LOG_FILE}"
256 local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}"
257
258 mount_image
259 sudo cp ${log_file} "${OUTPUT_DIR}"
260 sudo cp ${exit_status_file} "${OUTPUT_DIR}"
261 sudo rm -f ${log_file}
262 unmount_image
263}
264
265is_rel_path()
266{
267 local path="$1"
268
269 [[ ${path:0:1} != "/" ]]
270}
271
272update_kconfig()
273{
274 local kconfig_file="$1"
275 local update_command="curl -sLf ${KCONFIG_URL} -o ${kconfig_file}"
276 # Github does not return the "last-modified" header when retrieving the
277 # raw contents of the file. Use the API call to get the last-modified
278 # time of the kernel config and only update the config if it has been
279 # updated after the previously cached config was created. This avoids
280 # unnecessarily compiling the kernel and selftests.
281 if [[ -f "${kconfig_file}" ]]; then
282 local last_modified_date="$(curl -sL -D - "${KCONFIG_API_URL}" -o /dev/null | \
283 grep "last-modified" | awk -F ': ' '{print $2}')"
284 local remote_modified_timestamp="$(date -d "${last_modified_date}" +"%s")"
285 local local_creation_timestamp="$(stat -c %Y "${kconfig_file}")"
286
287 if [[ "${remote_modified_timestamp}" -gt "${local_creation_timestamp}" ]]; then
288 ${update_command}
289 fi
290 else
291 ${update_command}
292 fi
293}
294
295main()
296{
297 local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
298 local kernel_checkout=$(realpath "${script_dir}"/../../../../)
299 # By default the script searches for the kernel in the checkout directory but
300 # it also obeys environment variables O= and KBUILD_OUTPUT=
301 local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
302 local command="${DEFAULT_COMMAND}"
303 local update_image="no"
304 local exit_command="poweroff -f"
305 local debug_shell="no"
306
307 while getopts 'hskid:j:' opt; do
308 case ${opt} in
309 i)
310 update_image="yes"
311 ;;
312 d)
313 OUTPUT_DIR="$OPTARG"
314 ;;
315 j)
316 NUM_COMPILE_JOBS="$OPTARG"
317 ;;
318 s)
319 command=""
320 debug_shell="yes"
321 exit_command="bash"
322 ;;
323 h)
324 usage
325 exit 0
326 ;;
327 \? )
328 echo "Invalid Option: -$OPTARG"
329 usage
330 exit 1
331 ;;
332 : )
333 echo "Invalid Option: -$OPTARG requires an argument"
334 usage
335 exit 1
336 ;;
337 esac
338 done
339 shift $((OPTIND -1))
340
341 if [[ $# -eq 0 && "${debug_shell}" == "no" ]]; then
342 echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
343 else
344 command="$@"
345 fi
346
347 local kconfig_file="${OUTPUT_DIR}/latest.config"
348 local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
349
350 # Figure out where the kernel is being built.
351 # O takes precedence over KBUILD_OUTPUT.
352 if [[ "${O:=""}" != "" ]]; then
353 if is_rel_path "${O}"; then
354 O="$(realpath "${PWD}/${O}")"
355 fi
356 kernel_bzimage="${O}/${BZIMAGE}"
357 make_command="${make_command} O=${O}"
358 elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
359 if is_rel_path "${KBUILD_OUTPUT}"; then
360 KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
361 fi
362 kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
363 make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
364 fi
365
366 populate_url_map
367
368 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
369 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
370
371 echo "Output directory: ${OUTPUT_DIR}"
372
373 mkdir -p "${OUTPUT_DIR}"
374 mkdir -p "${mount_dir}"
375 update_kconfig "${kconfig_file}"
376
377 recompile_kernel "${kernel_checkout}" "${make_command}"
378
379 if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then
380 echo "rootfs image not found in ${rootfs_img}"
381 update_image="yes"
382 fi
383
384 if [[ "${update_image}" == "yes" ]]; then
385 create_vm_image
386 fi
387
388 update_selftests "${kernel_checkout}" "${make_command}"
389 update_init_script "${command}" "${exit_command}"
390 run_vm "${kernel_bzimage}"
391 if [[ "${command}" != "" ]]; then
392 copy_logs
393 echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
394 fi
395}
396
397catch()
398{
399 local exit_code=$1
400 local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}"
401 # This is just a cleanup and the directory may
402 # have already been unmounted. So, don't let this
403 # clobber the error code we intend to return.
404 unmount_image || true
405 if [[ -f "${exit_status_file}" ]]; then
406 exit_code="$(cat ${exit_status_file})"
407 fi
408 exit ${exit_code}
409}
410
411trap 'catch "$?"' EXIT
412
413main "$@"