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