Linux Audio

Check our new training course

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