1#!/bin/sh -e 2 3# Upload a created tarball to Coverity Scan, as per 4# https://scan.coverity.com/projects/qemu/builds/new 5 6# This work is licensed under the terms of the GNU GPL version 2, 7# or (at your option) any later version. 8# See the COPYING file in the top-level directory. 9# 10# Copyright (c) 2017-2020 Linaro Limited 11# Written by Peter Maydell 12 13# Note that this script will automatically download and 14# run the (closed-source) coverity build tools, so don't 15# use it if you don't trust them! 16 17# This script assumes that you're running it from a QEMU source 18# tree, and that tree is a fresh clean one, because we do an in-tree 19# build. (This is necessary so that the filenames that the Coverity 20# Scan server sees are relative paths that match up with the component 21# regular expressions it uses; an out-of-tree build won't work for this.) 22# The host machine should have as many of QEMU's dependencies 23# installed as possible, for maximum coverity coverage. 24 25# To do an upload you need to be a maintainer in the Coverity online 26# service, and you will need to know the "Coverity token", which is a 27# secret 8 digit hex string. You can find that from the web UI in the 28# project settings, if you have maintainer access there. 29 30# Command line options: 31# --dry-run : run the tools, but don't actually do the upload 32# --docker : create and work inside a container 33# --docker-engine : specify the container engine to use (docker/podman/auto); 34# implies --docker 35# --update-tools-only : update the cached copy of the tools, but don't run them 36# --no-update-tools : do not update the cached copy of the tools 37# --tokenfile : file to read Coverity token from 38# --version ver : specify version being analyzed (default: ask git) 39# --description desc : specify description of this version (default: ask git) 40# --srcdir : QEMU source tree to analyze (default: current working dir) 41# --results-tarball : path to copy the results tarball to (default: don't 42# copy it anywhere, just upload it) 43# --src-tarball : tarball to untar into src dir (default: none); this 44# is intended mainly for internal use by the Docker support 45# 46# User-specifiable environment variables: 47# COVERITY_TOKEN -- Coverity token (default: looks at your 48# coverity.token config) 49# COVERITY_EMAIL -- the email address to use for uploads (default: 50# looks at your git coverity.email or user.email config) 51# COVERITY_BUILD_CMD -- make command (default: 'make -jN' where N is 52# number of CPUs as determined by 'nproc') 53# COVERITY_TOOL_BASE -- set to directory to put coverity tools 54# (default: /tmp/coverity-tools) 55# 56# You must specify the token, either by environment variable or by 57# putting it in a file and using --tokenfile. Everything else has 58# a reasonable default if this is run from a git tree. 59 60check_upload_permissions() { 61 # Check whether we can do an upload to the server; will exit the script 62 # with status 1 if the check failed (usually a bad token); 63 # will exit the script with status 0 if the check indicated that we 64 # can't upload yet (ie we are at quota) 65 # Assumes that COVERITY_TOKEN, PROJNAME and DRYRUN have been initialized. 66 67 echo "Checking upload permissions..." 68 69 if ! up_perm="$(wget https://scan.coverity.com/api/upload_permitted --post-data "token=$COVERITY_TOKEN&project=$PROJNAME" -q -O -)"; then 70 echo "Coverity Scan API access denied: bad token?" 71 exit 1 72 fi 73 74 # Really up_perm is a JSON response with either 75 # {upload_permitted:true} or {next_upload_permitted_at:<date>} 76 # We do some hacky string parsing instead of properly parsing it. 77 case "$up_perm" in 78 *upload_permitted*true*) 79 echo "Coverity Scan: upload permitted" 80 ;; 81 *next_upload_permitted_at*) 82 if [ "$DRYRUN" = yes ]; then 83 echo "Coverity Scan: upload quota reached, continuing dry run" 84 else 85 echo "Coverity Scan: upload quota reached; stopping here" 86 # Exit success as this isn't a build error. 87 exit 0 88 fi 89 ;; 90 *) 91 echo "Coverity Scan upload check: unexpected result $up_perm" 92 exit 1 93 ;; 94 esac 95} 96 97 98build_docker_image() { 99 # build docker container including the coverity-scan tools 100 echo "Building docker container..." 101 # TODO: This re-unpacks the tools every time, rather than caching 102 # and reusing the image produced by the COPY of the .tgz file. 103 # Not sure why. 104 tests/docker/docker.py --engine ${DOCKER_ENGINE} build \ 105 -t coverity-scanner -f scripts/coverity-scan/coverity-scan.docker \ 106 --extra-files scripts/coverity-scan/run-coverity-scan \ 107 "$COVERITY_TOOL_BASE"/coverity_tool.tgz 108} 109 110update_coverity_tools () { 111 # Check for whether we need to download the Coverity tools 112 # (either because we don't have a copy, or because it's out of date) 113 # Assumes that COVERITY_TOOL_BASE, COVERITY_TOKEN and PROJNAME are set. 114 115 mkdir -p "$COVERITY_TOOL_BASE" 116 cd "$COVERITY_TOOL_BASE" 117 118 echo "Checking for new version of coverity build tools..." 119 wget https://scan.coverity.com/download/cxx/linux64 --post-data "token=$COVERITY_TOKEN&project=$PROJNAME&md5=1" -O coverity_tool.md5.new 120 121 if ! cmp -s coverity_tool.md5 coverity_tool.md5.new; then 122 # out of date md5 or no md5: download new build tool 123 # blow away the old build tool 124 echo "Downloading coverity build tools..." 125 rm -rf coverity_tool coverity_tool.tgz 126 wget https://scan.coverity.com/download/cxx/linux64 --post-data "token=$COVERITY_TOKEN&project=$PROJNAME" -O coverity_tool.tgz 127 if ! (cat coverity_tool.md5.new; echo " coverity_tool.tgz") | md5sum -c --status; then 128 echo "Downloaded tarball didn't match md5sum!" 129 exit 1 130 fi 131 132 if [ "$DOCKER" != yes ]; then 133 # extract the new one, keeping it corralled in a 'coverity_tool' directory 134 echo "Unpacking coverity build tools..." 135 mkdir -p coverity_tool 136 cd coverity_tool 137 tar xf ../coverity_tool.tgz 138 cd .. 139 mv coverity_tool.md5.new coverity_tool.md5 140 fi 141 fi 142 rm -f coverity_tool.md5.new 143 cd "$SRCDIR" 144 145 if [ "$DOCKER" = yes ]; then 146 build_docker_image 147 fi 148} 149 150 151# Check user-provided environment variables and arguments 152DRYRUN=no 153UPDATE=yes 154DOCKER=no 155 156while [ "$#" -ge 1 ]; do 157 case "$1" in 158 --dry-run) 159 shift 160 DRYRUN=yes 161 ;; 162 --no-update-tools) 163 shift 164 UPDATE=no 165 ;; 166 --update-tools-only) 167 shift 168 UPDATE=only 169 ;; 170 --version) 171 shift 172 if [ $# -eq 0 ]; then 173 echo "--version needs an argument" 174 exit 1 175 fi 176 VERSION="$1" 177 shift 178 ;; 179 --description) 180 shift 181 if [ $# -eq 0 ]; then 182 echo "--description needs an argument" 183 exit 1 184 fi 185 DESCRIPTION="$1" 186 shift 187 ;; 188 --tokenfile) 189 shift 190 if [ $# -eq 0 ]; then 191 echo "--tokenfile needs an argument" 192 exit 1 193 fi 194 COVERITY_TOKEN="$(cat "$1")" 195 shift 196 ;; 197 --srcdir) 198 shift 199 if [ $# -eq 0 ]; then 200 echo "--srcdir needs an argument" 201 exit 1 202 fi 203 SRCDIR="$1" 204 shift 205 ;; 206 --results-tarball) 207 shift 208 if [ $# -eq 0 ]; then 209 echo "--results-tarball needs an argument" 210 exit 1 211 fi 212 RESULTSTARBALL="$1" 213 shift 214 ;; 215 --src-tarball) 216 shift 217 if [ $# -eq 0 ]; then 218 echo "--src-tarball needs an argument" 219 exit 1 220 fi 221 SRCTARBALL="$1" 222 shift 223 ;; 224 --docker) 225 DOCKER=yes 226 DOCKER_ENGINE=auto 227 shift 228 ;; 229 --docker-engine) 230 shift 231 if [ $# -eq 0 ]; then 232 echo "--docker-engine needs an argument" 233 exit 1 234 fi 235 DOCKER=yes 236 DOCKER_ENGINE="$1" 237 shift 238 ;; 239 *) 240 echo "Unexpected argument '$1'" 241 exit 1 242 ;; 243 esac 244done 245 246if [ -z "$COVERITY_TOKEN" ]; then 247 COVERITY_TOKEN="$(git config coverity.token)" 248fi 249if [ -z "$COVERITY_TOKEN" ]; then 250 echo "COVERITY_TOKEN environment variable not set" 251 exit 1 252fi 253 254if [ -z "$COVERITY_BUILD_CMD" ]; then 255 NPROC=$(nproc) 256 COVERITY_BUILD_CMD="make -j$NPROC" 257 echo "COVERITY_BUILD_CMD: using default '$COVERITY_BUILD_CMD'" 258fi 259 260if [ -z "$COVERITY_TOOL_BASE" ]; then 261 echo "COVERITY_TOOL_BASE: using default /tmp/coverity-tools" 262 COVERITY_TOOL_BASE=/tmp/coverity-tools 263fi 264 265if [ -z "$SRCDIR" ]; then 266 SRCDIR="$PWD" 267fi 268 269PROJNAME=QEMU 270TARBALL=cov-int.tar.xz 271 272if [ "$UPDATE" = only ]; then 273 # Just do the tools update; we don't need to check whether 274 # we are in a source tree or have upload rights for this, 275 # so do it before some of the command line and source tree checks. 276 277 if [ "$DOCKER" = yes ] && [ ! -z "$SRCTARBALL" ]; then 278 echo --update-tools-only --docker is incompatible with --src-tarball. 279 exit 1 280 fi 281 282 update_coverity_tools 283 exit 0 284fi 285 286if [ ! -e "$SRCDIR" ]; then 287 mkdir "$SRCDIR" 288fi 289 290cd "$SRCDIR" 291 292if [ ! -z "$SRCTARBALL" ]; then 293 echo "Untarring source tarball into $SRCDIR..." 294 tar xvf "$SRCTARBALL" 295fi 296 297echo "Checking this is a QEMU source tree..." 298if ! [ -e "$SRCDIR/VERSION" ]; then 299 echo "Not in a QEMU source tree?" 300 exit 1 301fi 302 303# Fill in defaults used by the non-update-only process 304if [ -z "$VERSION" ]; then 305 VERSION="$(git describe --always HEAD)" 306fi 307 308if [ -z "$DESCRIPTION" ]; then 309 DESCRIPTION="$(git rev-parse HEAD)" 310fi 311 312if [ -z "$COVERITY_EMAIL" ]; then 313 COVERITY_EMAIL="$(git config coverity.email)" 314fi 315if [ -z "$COVERITY_EMAIL" ]; then 316 COVERITY_EMAIL="$(git config user.email)" 317fi 318 319# Otherwise, continue with the full build and upload process. 320 321check_upload_permissions 322 323if [ "$UPDATE" != no ]; then 324 update_coverity_tools 325fi 326 327# Run ourselves inside docker if that's what the user wants 328if [ "$DOCKER" = yes ]; then 329 # Put the Coverity token into a temporary file that only 330 # we have read access to, and then pass it to docker build 331 # using a volume. A volume is enough for the token not to 332 # leak into the Docker image. 333 umask 077 334 SECRETDIR=$(mktemp -d) 335 if [ -z "$SECRETDIR" ]; then 336 echo "Failed to create temporary directory" 337 exit 1 338 fi 339 trap 'rm -rf "$SECRETDIR"' INT TERM EXIT 340 echo "Created temporary directory $SECRETDIR" 341 SECRET="$SECRETDIR/token" 342 echo "$COVERITY_TOKEN" > "$SECRET" 343 echo "Archiving sources to be analyzed..." 344 ./scripts/archive-source.sh "$SECRETDIR/qemu-sources.tgz" 345 ARGS="--no-update-tools" 346 if [ "$DRYRUN" = yes ]; then 347 ARGS="$ARGS --dry-run" 348 fi 349 echo "Running scanner..." 350 # If we need to capture the output tarball, get the inner run to 351 # save it to the secrets directory so we can copy it out before the 352 # directory is cleaned up. 353 if [ ! -z "$RESULTSTARBALL" ]; then 354 ARGS="$ARGS --results-tarball /work/cov-int.tar.xz" 355 fi 356 # Arrange for this docker run to get access to the sources with -v. 357 # We pass through all the configuration from the outer script to the inner. 358 export COVERITY_EMAIL COVERITY_BUILD_CMD 359 tests/docker/docker.py run -it --env COVERITY_EMAIL --env COVERITY_BUILD_CMD \ 360 -v "$SECRETDIR:/work" coverity-scanner \ 361 ./run-coverity-scan --version "$VERSION" \ 362 --description "$DESCRIPTION" $ARGS --tokenfile /work/token \ 363 --srcdir /qemu --src-tarball /work/qemu-sources.tgz 364 if [ ! -z "$RESULTSTARBALL" ]; then 365 echo "Copying results tarball to $RESULTSTARBALL..." 366 cp "$SECRETDIR/cov-int.tar.xz" "$RESULTSTARBALL" 367 fi 368 echo "Docker work complete." 369 exit 0 370fi 371 372TOOLBIN="$(cd "$COVERITY_TOOL_BASE" && echo $PWD/coverity_tool/cov-analysis-*/bin)" 373 374if ! test -x "$TOOLBIN/cov-build"; then 375 echo "Couldn't find cov-build in the coverity build-tool directory??" 376 exit 1 377fi 378 379export PATH="$TOOLBIN:$PATH" 380 381cd "$SRCDIR" 382 383echo "Nuking build directory..." 384rm -rf +build 385mkdir +build 386cd +build 387 388echo "Configuring..." 389# We configure with a fixed set of enables here to ensure that we don't 390# accidentally reduce the scope of the analysis by doing the build on 391# the system that's missing a dependency that we need to build part of 392# the codebase. 393../configure --disable-modules --enable-sdl --enable-gtk \ 394 --enable-opengl --enable-vte --enable-gnutls \ 395 --enable-nettle --enable-curses --enable-curl \ 396 --audio-drv-list=oss,alsa,sdl,pa --enable-virtfs \ 397 --enable-vnc --enable-vnc-sasl --enable-vnc-jpeg --enable-png \ 398 --enable-xen --enable-brlapi \ 399 --enable-linux-aio --enable-attr \ 400 --enable-cap-ng --enable-trace-backends=log --enable-spice --enable-rbd \ 401 --enable-libusb --enable-usb-redir \ 402 --enable-libiscsi --enable-libnfs --enable-seccomp \ 403 --enable-tpm --enable-libssh --enable-lzo --enable-snappy --enable-bzip2 \ 404 --enable-numa --enable-rdma --enable-smartcard --enable-virglrenderer \ 405 --enable-mpath --enable-glusterfs \ 406 --enable-virtfs --enable-zstd 407 408echo "Running cov-build..." 409rm -rf cov-int 410mkdir cov-int 411cov-build --dir cov-int $COVERITY_BUILD_CMD 412 413echo "Creating results tarball..." 414tar cvf - cov-int | xz > "$TARBALL" 415 416if [ ! -z "$RESULTSTARBALL" ]; then 417 echo "Copying results tarball to $RESULTSTARBALL..." 418 cp "$TARBALL" "$RESULTSTARBALL" 419fi 420 421echo "Uploading results tarball..." 422 423if [ "$DRYRUN" = yes ]; then 424 echo "Dry run only, not uploading $TARBALL" 425 exit 0 426fi 427 428curl --form token="$COVERITY_TOKEN" --form email="$COVERITY_EMAIL" \ 429 --form file=@"$TARBALL" --form version="$VERSION" \ 430 --form description="$DESCRIPTION" \ 431 https://scan.coverity.com/builds?project="$PROJNAME" 432 433echo "Done." 434