diff options
Diffstat (limited to 'scripts/coverity-scan/run-coverity-scan')
-rwxr-xr-x | scripts/coverity-scan/run-coverity-scan | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/scripts/coverity-scan/run-coverity-scan b/scripts/coverity-scan/run-coverity-scan new file mode 100755 index 000000000..7395bbfad --- /dev/null +++ b/scripts/coverity-scan/run-coverity-scan @@ -0,0 +1,433 @@ +#!/bin/sh -e + +# Upload a created tarball to Coverity Scan, as per +# https://scan.coverity.com/projects/qemu/builds/new + +# This work is licensed under the terms of the GNU GPL version 2, +# or (at your option) any later version. +# See the COPYING file in the top-level directory. +# +# Copyright (c) 2017-2020 Linaro Limited +# Written by Peter Maydell + +# Note that this script will automatically download and +# run the (closed-source) coverity build tools, so don't +# use it if you don't trust them! + +# This script assumes that you're running it from a QEMU source +# tree, and that tree is a fresh clean one, because we do an in-tree +# build. (This is necessary so that the filenames that the Coverity +# Scan server sees are relative paths that match up with the component +# regular expressions it uses; an out-of-tree build won't work for this.) +# The host machine should have as many of QEMU's dependencies +# installed as possible, for maximum coverity coverage. + +# To do an upload you need to be a maintainer in the Coverity online +# service, and you will need to know the "Coverity token", which is a +# secret 8 digit hex string. You can find that from the web UI in the +# project settings, if you have maintainer access there. + +# Command line options: +# --dry-run : run the tools, but don't actually do the upload +# --docker : create and work inside a container +# --docker-engine : specify the container engine to use (docker/podman/auto); +# implies --docker +# --update-tools-only : update the cached copy of the tools, but don't run them +# --no-update-tools : do not update the cached copy of the tools +# --tokenfile : file to read Coverity token from +# --version ver : specify version being analyzed (default: ask git) +# --description desc : specify description of this version (default: ask git) +# --srcdir : QEMU source tree to analyze (default: current working dir) +# --results-tarball : path to copy the results tarball to (default: don't +# copy it anywhere, just upload it) +# --src-tarball : tarball to untar into src dir (default: none); this +# is intended mainly for internal use by the Docker support +# +# User-specifiable environment variables: +# COVERITY_TOKEN -- Coverity token (default: looks at your +# coverity.token config) +# COVERITY_EMAIL -- the email address to use for uploads (default: +# looks at your git coverity.email or user.email config) +# COVERITY_BUILD_CMD -- make command (default: 'make -jN' where N is +# number of CPUs as determined by 'nproc') +# COVERITY_TOOL_BASE -- set to directory to put coverity tools +# (default: /tmp/coverity-tools) +# +# You must specify the token, either by environment variable or by +# putting it in a file and using --tokenfile. Everything else has +# a reasonable default if this is run from a git tree. + +check_upload_permissions() { + # Check whether we can do an upload to the server; will exit the script + # with status 1 if the check failed (usually a bad token); + # will exit the script with status 0 if the check indicated that we + # can't upload yet (ie we are at quota) + # Assumes that COVERITY_TOKEN, PROJNAME and DRYRUN have been initialized. + + echo "Checking upload permissions..." + + if ! up_perm="$(wget https://scan.coverity.com/api/upload_permitted --post-data "token=$COVERITY_TOKEN&project=$PROJNAME" -q -O -)"; then + echo "Coverity Scan API access denied: bad token?" + exit 1 + fi + + # Really up_perm is a JSON response with either + # {upload_permitted:true} or {next_upload_permitted_at:<date>} + # We do some hacky string parsing instead of properly parsing it. + case "$up_perm" in + *upload_permitted*true*) + echo "Coverity Scan: upload permitted" + ;; + *next_upload_permitted_at*) + if [ "$DRYRUN" = yes ]; then + echo "Coverity Scan: upload quota reached, continuing dry run" + else + echo "Coverity Scan: upload quota reached; stopping here" + # Exit success as this isn't a build error. + exit 0 + fi + ;; + *) + echo "Coverity Scan upload check: unexpected result $up_perm" + exit 1 + ;; + esac +} + + +build_docker_image() { + # build docker container including the coverity-scan tools + echo "Building docker container..." + # TODO: This re-unpacks the tools every time, rather than caching + # and reusing the image produced by the COPY of the .tgz file. + # Not sure why. + tests/docker/docker.py --engine ${DOCKER_ENGINE} build \ + -t coverity-scanner -f scripts/coverity-scan/coverity-scan.docker \ + --extra-files scripts/coverity-scan/run-coverity-scan \ + "$COVERITY_TOOL_BASE"/coverity_tool.tgz +} + +update_coverity_tools () { + # Check for whether we need to download the Coverity tools + # (either because we don't have a copy, or because it's out of date) + # Assumes that COVERITY_TOOL_BASE, COVERITY_TOKEN and PROJNAME are set. + + mkdir -p "$COVERITY_TOOL_BASE" + cd "$COVERITY_TOOL_BASE" + + echo "Checking for new version of coverity build tools..." + wget https://scan.coverity.com/download/linux64 --post-data "token=$COVERITY_TOKEN&project=$PROJNAME&md5=1" -O coverity_tool.md5.new + + if ! cmp -s coverity_tool.md5 coverity_tool.md5.new; then + # out of date md5 or no md5: download new build tool + # blow away the old build tool + echo "Downloading coverity build tools..." + rm -rf coverity_tool coverity_tool.tgz + wget https://scan.coverity.com/download/linux64 --post-data "token=$COVERITY_TOKEN&project=$PROJNAME" -O coverity_tool.tgz + if ! (cat coverity_tool.md5.new; echo " coverity_tool.tgz") | md5sum -c --status; then + echo "Downloaded tarball didn't match md5sum!" + exit 1 + fi + + if [ "$DOCKER" != yes ]; then + # extract the new one, keeping it corralled in a 'coverity_tool' directory + echo "Unpacking coverity build tools..." + mkdir -p coverity_tool + cd coverity_tool + tar xf ../coverity_tool.tgz + cd .. + mv coverity_tool.md5.new coverity_tool.md5 + fi + fi + rm -f coverity_tool.md5.new + cd "$SRCDIR" + + if [ "$DOCKER" = yes ]; then + build_docker_image + fi +} + + +# Check user-provided environment variables and arguments +DRYRUN=no +UPDATE=yes +DOCKER=no + +while [ "$#" -ge 1 ]; do + case "$1" in + --dry-run) + shift + DRYRUN=yes + ;; + --no-update-tools) + shift + UPDATE=no + ;; + --update-tools-only) + shift + UPDATE=only + ;; + --version) + shift + if [ $# -eq 0 ]; then + echo "--version needs an argument" + exit 1 + fi + VERSION="$1" + shift + ;; + --description) + shift + if [ $# -eq 0 ]; then + echo "--description needs an argument" + exit 1 + fi + DESCRIPTION="$1" + shift + ;; + --tokenfile) + shift + if [ $# -eq 0 ]; then + echo "--tokenfile needs an argument" + exit 1 + fi + COVERITY_TOKEN="$(cat "$1")" + shift + ;; + --srcdir) + shift + if [ $# -eq 0 ]; then + echo "--srcdir needs an argument" + exit 1 + fi + SRCDIR="$1" + shift + ;; + --results-tarball) + shift + if [ $# -eq 0 ]; then + echo "--results-tarball needs an argument" + exit 1 + fi + RESULTSTARBALL="$1" + shift + ;; + --src-tarball) + shift + if [ $# -eq 0 ]; then + echo "--src-tarball needs an argument" + exit 1 + fi + SRCTARBALL="$1" + shift + ;; + --docker) + DOCKER=yes + DOCKER_ENGINE=auto + shift + ;; + --docker-engine) + shift + if [ $# -eq 0 ]; then + echo "--docker-engine needs an argument" + exit 1 + fi + DOCKER=yes + DOCKER_ENGINE="$1" + shift + ;; + *) + echo "Unexpected argument '$1'" + exit 1 + ;; + esac +done + +if [ -z "$COVERITY_TOKEN" ]; then + COVERITY_TOKEN="$(git config coverity.token)" +fi +if [ -z "$COVERITY_TOKEN" ]; then + echo "COVERITY_TOKEN environment variable not set" + exit 1 +fi + +if [ -z "$COVERITY_BUILD_CMD" ]; then + NPROC=$(nproc) + COVERITY_BUILD_CMD="make -j$NPROC" + echo "COVERITY_BUILD_CMD: using default '$COVERITY_BUILD_CMD'" +fi + +if [ -z "$COVERITY_TOOL_BASE" ]; then + echo "COVERITY_TOOL_BASE: using default /tmp/coverity-tools" + COVERITY_TOOL_BASE=/tmp/coverity-tools +fi + +if [ -z "$SRCDIR" ]; then + SRCDIR="$PWD" +fi + +PROJNAME=QEMU +TARBALL=cov-int.tar.xz + +if [ "$UPDATE" = only ]; then + # Just do the tools update; we don't need to check whether + # we are in a source tree or have upload rights for this, + # so do it before some of the command line and source tree checks. + + if [ "$DOCKER" = yes ] && [ ! -z "$SRCTARBALL" ]; then + echo --update-tools-only --docker is incompatible with --src-tarball. + exit 1 + fi + + update_coverity_tools + exit 0 +fi + +if [ ! -e "$SRCDIR" ]; then + mkdir "$SRCDIR" +fi + +cd "$SRCDIR" + +if [ ! -z "$SRCTARBALL" ]; then + echo "Untarring source tarball into $SRCDIR..." + tar xvf "$SRCTARBALL" +fi + +echo "Checking this is a QEMU source tree..." +if ! [ -e "$SRCDIR/VERSION" ]; then + echo "Not in a QEMU source tree?" + exit 1 +fi + +# Fill in defaults used by the non-update-only process +if [ -z "$VERSION" ]; then + VERSION="$(git describe --always HEAD)" +fi + +if [ -z "$DESCRIPTION" ]; then + DESCRIPTION="$(git rev-parse HEAD)" +fi + +if [ -z "$COVERITY_EMAIL" ]; then + COVERITY_EMAIL="$(git config coverity.email)" +fi +if [ -z "$COVERITY_EMAIL" ]; then + COVERITY_EMAIL="$(git config user.email)" +fi + +# Otherwise, continue with the full build and upload process. + +check_upload_permissions + +if [ "$UPDATE" != no ]; then + update_coverity_tools +fi + +# Run ourselves inside docker if that's what the user wants +if [ "$DOCKER" = yes ]; then + # Put the Coverity token into a temporary file that only + # we have read access to, and then pass it to docker build + # using a volume. A volume is enough for the token not to + # leak into the Docker image. + umask 077 + SECRETDIR=$(mktemp -d) + if [ -z "$SECRETDIR" ]; then + echo "Failed to create temporary directory" + exit 1 + fi + trap 'rm -rf "$SECRETDIR"' INT TERM EXIT + echo "Created temporary directory $SECRETDIR" + SECRET="$SECRETDIR/token" + echo "$COVERITY_TOKEN" > "$SECRET" + echo "Archiving sources to be analyzed..." + ./scripts/archive-source.sh "$SECRETDIR/qemu-sources.tgz" + ARGS="--no-update-tools" + if [ "$DRYRUN" = yes ]; then + ARGS="$ARGS --dry-run" + fi + echo "Running scanner..." + # If we need to capture the output tarball, get the inner run to + # save it to the secrets directory so we can copy it out before the + # directory is cleaned up. + if [ ! -z "$RESULTSTARBALL" ]; then + ARGS="$ARGS --results-tarball /work/cov-int.tar.xz" + fi + # Arrange for this docker run to get access to the sources with -v. + # We pass through all the configuration from the outer script to the inner. + export COVERITY_EMAIL COVERITY_BUILD_CMD + tests/docker/docker.py run -it --env COVERITY_EMAIL --env COVERITY_BUILD_CMD \ + -v "$SECRETDIR:/work" coverity-scanner \ + ./run-coverity-scan --version "$VERSION" \ + --description "$DESCRIPTION" $ARGS --tokenfile /work/token \ + --srcdir /qemu --src-tarball /work/qemu-sources.tgz + if [ ! -z "$RESULTSTARBALL" ]; then + echo "Copying results tarball to $RESULTSTARBALL..." + cp "$SECRETDIR/cov-int.tar.xz" "$RESULTSTARBALL" + fi + echo "Docker work complete." + exit 0 +fi + +TOOLBIN="$(cd "$COVERITY_TOOL_BASE" && echo $PWD/coverity_tool/cov-analysis-*/bin)" + +if ! test -x "$TOOLBIN/cov-build"; then + echo "Couldn't find cov-build in the coverity build-tool directory??" + exit 1 +fi + +export PATH="$TOOLBIN:$PATH" + +cd "$SRCDIR" + +echo "Nuking build directory..." +rm -rf +build +mkdir +build +cd +build + +echo "Configuring..." +# We configure with a fixed set of enables here to ensure that we don't +# accidentally reduce the scope of the analysis by doing the build on +# the system that's missing a dependency that we need to build part of +# the codebase. +../configure --disable-modules --enable-sdl --enable-gtk \ + --enable-opengl --enable-vte --enable-gnutls \ + --enable-nettle --enable-curses --enable-curl \ + --audio-drv-list=oss,alsa,sdl,pa --enable-virtfs \ + --enable-vnc --enable-vnc-sasl --enable-vnc-jpeg --enable-vnc-png \ + --enable-xen --enable-brlapi \ + --enable-linux-aio --enable-attr \ + --enable-cap-ng --enable-trace-backends=log --enable-spice --enable-rbd \ + --enable-xfsctl --enable-libusb --enable-usb-redir \ + --enable-libiscsi --enable-libnfs --enable-seccomp \ + --enable-tpm --enable-libssh --enable-lzo --enable-snappy --enable-bzip2 \ + --enable-numa --enable-rdma --enable-smartcard --enable-virglrenderer \ + --enable-mpath --enable-libxml2 --enable-glusterfs \ + --enable-virtfs --enable-zstd + +echo "Running cov-build..." +rm -rf cov-int +mkdir cov-int +cov-build --dir cov-int $COVERITY_BUILD_CMD + +echo "Creating results tarball..." +tar cvf - cov-int | xz > "$TARBALL" + +if [ ! -z "$RESULTSTARBALL" ]; then + echo "Copying results tarball to $RESULTSTARBALL..." + cp "$TARBALL" "$RESULTSTARBALL" +fi + +echo "Uploading results tarball..." + +if [ "$DRYRUN" = yes ]; then + echo "Dry run only, not uploading $TARBALL" + exit 0 +fi + +curl --form token="$COVERITY_TOKEN" --form email="$COVERITY_EMAIL" \ + --form file=@"$TARBALL" --form version="$VERSION" \ + --form description="$DESCRIPTION" \ + https://scan.coverity.com/builds?project="$PROJNAME" + +echo "Done." |