#!/bin/bash

function usage {
cat <<EOF

Usage: `basename $0` -h # Displays this usage/help text
Usage: `basename $0` -x # lists exit codes
Usage: `basename $0` [-n] [-c] [-d] [-a]
           [-p product]
           [--hgtool hgtool_location]
           [-u hg_ssh_user]
           [-k hg_ssh_key]
           [-r existing_repo_dir]
           # Use mozilla-central builds to check HSTS & HPKP
           [--use-mozilla-central]
           # Use archive.m.o instead of the taskcluster index to get xpcshell
           [--use-ftp-builds]
           # One (or more) of the following actions must be specified.
           --hsts | --hpkp | --blocklist
           -b branch

EOF
}

function exitcodes {
cat <<EOF

EXIT CODES for `basename $0`:
    0    Success (either no updates to push, or push was successful)
    2    Updates are available, but were not pushed due to dry-run mode.
   11    Unknown command-line option provided
   12    Branch not specified on command-line
   13    No update action specified on command-line
   14    Invalid product specified
   21    Unable to parse version from version.txt
   22    Unable to retrieve latest taskId
   31    Missing downloaded browser artifact
   32    Missing downloaded tests artifact
   41    Missing downloaded HSTS file
   42    Generated HSTS preload list is empty
   51    Missing downloaded HPKP file
   52    Generated HPKP preload list is empty
   60    xmllint is not in PATH
   61    Downloaded AMO blocklist file isn't valid XML
   62    Downloaded hg blocklist file isn't valid XML
   70    HSTS script failed
   71    HPKP script failed

EOF
}

DRY_RUN=false
PRODUCT="firefox"
BRANCH=""
PLATFORM="linux-x86_64"
TBOX_BUILDS_PLATFORM="linux64"
PLATFORM_EXT="tar.bz2"
UNPACK_CMD="tar jxf"
CLOSED_TREE=false
DONTBUILD=false
APPROVAL=false
HG_SSH_USER='ffxbld'
HG_SSH_KEY='~cltbld/.ssh/ffxbld_rsa'
REPODIR=''
HGTOOL="$(dirname "${0}")/../../buildfarm/utils/hgtool.py"
APP_DIR=''
APP_ID=''
APP_NAME=''
LOCALHOST=`/bin/hostname -s`
HGHOST="hg.mozilla.org"
STAGEHOST="archive.mozilla.org"
HG=hg
WGET="wget -nv"
UNZIP="unzip -q"
DIFF="diff -up"
BASEDIR=`pwd`
SCRIPTDIR=`dirname $0`
VERSION=''
MCVERSION=''
USE_MC=false
USE_TC=true
FLATTENED=true
JSONTOOL="python ${SCRIPTDIR}/../../buildfarm/utils/jsontool.py"

DO_HSTS=false
HSTS_PRELOAD_SCRIPT="getHSTSPreloadList.js"
HSTS_PRELOAD_ERRORS="nsSTSPreloadList.errors"
HSTS_PRELOAD_INC="nsSTSPreloadList.inc"
HSTS_UPDATED=false

DO_HPKP=false
HPKP_PRELOAD_SCRIPT="genHPKPStaticPins.js"
HPKP_PRELOAD_ERRORS="StaticHPKPins.errors"
HPKP_DER_TEST="default-ee.der"
HPKP_PRELOAD_JSON="PreloadedHPKPins.json"
HPKP_PRELOAD_OUTPUT="StaticHPKPins.h"
HPKP_UPDATED=false

DO_BLOCKLIST=false
BLOCKLIST_URL_AMO=''
BLOCKLIST_URL_HG=''
BLOCKLIST_UPDATED=false
BLOCKLIST_LOCAL_AMO="blocklist_amo.xml"
BLOCKLIST_LOCAL_HG="blocklist_hg.xml"

# Get the current in-tree version for a code branch.
function get_version {
    VERSION_REPO=$1
    VERSION_FILE='version.txt'

    cd "${BASEDIR}"
    VERSION_URL_HG="${VERSION_REPO}/raw-file/default/${APP_DIR}/config/version.txt"
    rm -f ${VERSION_FILE}
    ${WGET} -O ${VERSION_FILE} ${VERSION_URL_HG}
    WGET_STATUS=$?
    if [ ${WGET_STATUS} != 0 ]; then
        echo "ERROR: wget exited with a non-zero exit code: $WGET_STATUS" >&2
        exit ${WGET_STATUS}
    fi
    PARSED_VERSION=`cat version.txt`
    if [ "${PARSED_VERSION}" == "" ]; then
        echo "ERROR: Unable to parse version from $VERSION_FILE" >&2
        exit 21
    fi
    rm -f ${VERSION_FILE}
    echo ${PARSED_VERSION}
}

# Cleanup common artifacts.
function preflight_cleanup {
    cd "${BASEDIR}"
    rm -rf "${PRODUCT}" tests "${BROWSER_ARCHIVE}" "${TESTS_ARCHIVE}"
}

function download_shared_artifacts_from_ftp {
    cd "${BASEDIR}"

    # Download everything we need to run js with xpcshell
    echo "INFO: Downloading all the necessary pieces from ${STAGEHOST}..."
    POSSIBLE_ARTIFACT_DIRS="nightly/latest-${REPODIR}"
    if [ "${USE_MC}" == "true" ]; then
        POSSIBLE_ARTIFACT_DIRS="nightly/latest-mozilla-central"
    fi
    for ARTIFACT_DIR in ${POSSIBLE_ARTIFACT_DIRS}; do
        BROWSER_ARCHIVE_URL="https://${STAGEHOST}/pub/mozilla.org/${PRODUCT}/${ARTIFACT_DIR}/${BROWSER_ARCHIVE}"
        TESTS_ARCHIVE_URL="https://${STAGEHOST}/pub/mozilla.org/${PRODUCT}/${ARTIFACT_DIR}/${TESTS_ARCHIVE}"

	echo "INFO: ${WGET} ${BROWSER_ARCHIVE_URL}"
        ${WGET} ${BROWSER_ARCHIVE_URL}
	echo "INFO: ${WGET} ${TESTS_ARCHIVE_URL}"
        ${WGET} ${TESTS_ARCHIVE_URL}
        if [ -f ${BROWSER_ARCHIVE} -a -f ${TESTS_ARCHIVE} ]; then
            break
	fi
    done
}

function download_shared_artifacts_from_tc {
    cd "${BASEDIR}"
    TASKID_FILE="taskId.json"

    # Download everything we need to run js with xpcshell
    echo "INFO: Downloading all the necessary pieces from the taskcluster index..."
    TASKID_URL="https://index.taskcluster.net/v1/task/gecko.v2.${REPODIR}.latest.${PRODUCT}.linux64-opt"
    if [ "${USE_MC}" == "true" ]; then
        TASKID_URL="https://index.taskcluster.net/v1/task/gecko.v2.mozilla-central.latest.${PRODUCT}.linux64-opt"
    fi
    ${WGET} -O ${TASKID_FILE} ${TASKID_URL}
    TASKID=$($JSONTOOL -k taskId ${TASKID_FILE})
    if [ -z ${TASKID} ]; then
        echo "Failed to look up taskId at ${TASKID_URL}"
        exit 22
    else
        echo "INFO: Got taskId of $TASKID"
    fi
    # hack! really want the last run, which may not be the first (zeroth)
    BROWSER_ARCHIVE_URL="https://queue.taskcluster.net/v1/task/${TASKID}/runs/0/artifacts/public/build/${BROWSER_ARCHIVE}"
    TESTS_ARCHIVE_URL="https://queue.taskcluster.net/v1/task/${TASKID}/runs/0/artifacts/public/build/${TESTS_ARCHIVE}"
    echo "INFO: ${WGET} ${BROWSER_ARCHIVE_URL}"
    ${WGET} ${BROWSER_ARCHIVE_URL}
    echo "INFO: ${WGET} ${TESTS_ARCHIVE_URL}"
    ${WGET} ${TESTS_ARCHIVE_URL}
}

function unpack_artifacts {
    cd "${BASEDIR}"
    if [ ! -f ${BROWSER_ARCHIVE} ]; then
        echo "Downloaded file '${BROWSER_ARCHIVE}' not found in directory '$(pwd)'." >&2
        exit 31
    fi
    if [ ! -f ${TESTS_ARCHIVE} ]; then
        echo "Downloaded file '${TESTS_ARCHIVE}' not found in directory '$(pwd)'." >&2
        exit 32
    fi
    # Unpack the browser and move xpcshell in place for updating the preload list.
    echo "INFO: Unpacking resources..."
    ${UNPACK_CMD} "${BROWSER_ARCHIVE}"
    mkdir tests && cd tests
    ${UNZIP} "../${TESTS_ARCHIVE}"
    cd "${BASEDIR}"
    cp tests/bin/xpcshell "${PRODUCT}"
}

# gtk3 is required to run xpcshell as of Gecko 42.
function run_tooltool {
    sh ${SCRIPTDIR}/../tooltool/tooltool_wrapper.sh ${SCRIPTDIR}/periodic_file_updates.manifest https://api.pub.build.mozilla.org/tooltool/ setup.sh /builds/tooltool.py --authentication-file /builds/relengapi.tok
    LD_LIBRARY_PATH=${BASEDIR}/gtk3/usr/local/lib
    if [ -f "${BASEDIR}/gcc/lib/libstdc++.so" ]; then
        # We put both 32-bits and 64-bits library path in LD_LIBRARY_PATH: ld.so
        # will prefer the files in the 32-bits path when loading 32-bits executables,
        # and the files in the 64-bits path when loading 64-bits executables.
        LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}${BASEDIR}/gcc/lib64:${BASEDIR}/gcc/lib
    fi
}

# In bug 1164714, the public/src subdirectories were flattened away under security/manager.
# We need to check whether the HGREPO were processing has had that change uplifted yet so
# that we can find the files we need to update.
function is_flattened {
    # This URL will only be present in repos that have *not* been flattened.
    TEST_URL="${HGREPO}/raw-file/default/security/manager/boot/src/"
    HTTP_STATUS=`${WGET} --spider -S ${TEST_URL} 2>&1 | grep "HTTP/" | awk '{print $2}'`
    if [ "$HTTP_STATUS" == "200" ]; then
        export FLATTENED=false
    fi
}

# Downloads the current in-tree HSTS (HTTP Strict Transport Security) files.
# Runs a simple xpcshell script to generate up-to-date HSTS information.
# Compares the new HSTS output with the old to determine whether we need to update.
function compare_hsts_files {
    cd "${BASEDIR}"
    HSTS_PRELOAD_SCRIPT_HG="${HGREPO}/raw-file/default/security/manager/tools/${HSTS_PRELOAD_SCRIPT}"
    if [ "${FLATTENED}" == "true" ]; then
        HSTS_PRELOAD_ERRORS_HG="${HGREPO}/raw-file/default/security/manager/ssl/${HSTS_PRELOAD_ERRORS}"
        HSTS_PRELOAD_INC_HG="${HGREPO}/raw-file/default/security/manager/ssl/${HSTS_PRELOAD_INC}"
    else
        HSTS_PRELOAD_ERRORS_HG="${HGREPO}/raw-file/default/security/manager/boot/src/${HSTS_PRELOAD_ERRORS}"
        HSTS_PRELOAD_INC_HG="${HGREPO}/raw-file/default/security/manager/boot/src/${HSTS_PRELOAD_INC}"
    fi

    # Download everything we need: updater script, existing preload list and errors.
    echo "INFO: Downloading all the necessary pieces to update HSTS..."
    rm -rf "${HSTS_PRELOAD_SCRIPT}" "${HSTS_PRELOAD_ERRORS}" "${HSTS_PRELOAD_INC}"
    for URL in "${HSTS_PRELOAD_SCRIPT_HG}" "${HSTS_PRELOAD_ERRORS_HG}" "${HSTS_PRELOAD_INC_HG}"; do
        echo "INFO: ${WGET} ${URL}"
        ${WGET} ${URL}
        WGET_STATUS=$?
        if [ ${WGET_STATUS} != 0 ]; then
            echo "ERROR: wget exited with a non-zero exit code: ${WGET_STATUS}" >&2
            exit ${WGET_STATUS}
        fi
        if [ ! -f "$(basename "${URL}")" ]; then
            echo "Downloaded file '$(basename "${URL}")' not found in directory '$(pwd)' - this should have been downloaded above from ${URL}." >&2
            exit 41
        fi
    done

    # Run the script to get an updated preload list.
    echo "INFO: Generating new HSTS preload list..."
    cd "${BASEDIR}/${PRODUCT}"
    echo INFO: Running \"LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./xpcshell ${BASEDIR}/${HSTS_PRELOAD_SCRIPT} ${BASEDIR}/${HSTS_PRELOAD_INC}\"
    LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./xpcshell "${BASEDIR}/${HSTS_PRELOAD_SCRIPT}" "${BASEDIR}/${HSTS_PRELOAD_INC}"
    XPCSHELL_STATUS=$?
    if [ ${XPCSHELL_STATUS} != 0 ]; then
        cat ${HSTS_PRELOAD_ERRORS}
        echo "ERROR: xpcshell exited with a non-zero exit code: ${XPCSHELL_STATUS}" >&2
        exit 70
    fi

    # The created files should be non-empty.
    echo "INFO: Checking whether new HSTS preload list is valid..."
    if [ ! -s "${HSTS_PRELOAD_ERRORS}" ]; then
        echo "New HSTS preload error list ${HSTS_PRELOAD_ERRORS} is empty. I guess that's good?" >&2
    fi
    if [ ! -s "${HSTS_PRELOAD_INC}" ]; then
        echo "New HSTS preload list ${HSTS_PRELOAD_INC} is empty. That's less good." >&2
        exit 42
    fi
    cd "${BASEDIR}"

    # Check for differences
    echo "INFO: diffing old/new HSTS error lists..."
    ${DIFF} "${HSTS_PRELOAD_ERRORS}" "${PRODUCT}/${HSTS_PRELOAD_ERRORS}"
    DIFF_STATUS=$?
    case "${DIFF_STATUS}" in
        0) echo "INFO: New HSTS preload error list matches what is in-tree.";;
        1) echo "INFO: New HSTS preload error list differs from what is in-tree.";;
        *) echo "ERROR: diff exited with exit code: ${DIFF_STATUS}" >&2
           exit ${DIFF_STATUS}
    esac

    echo "INFO: diffing old/new HSTS preload lists..."
    ${DIFF} "${HSTS_PRELOAD_INC}" "${PRODUCT}/${HSTS_PRELOAD_INC}"
    DIFF_STATUS=$?
    case "${DIFF_STATUS}" in
        0) echo "INFO: New HSTS preload list matches what is in-tree.";;
        1) echo "INFO: New HSTS preload list differs from what is in-tree.";;
        *) echo "ERROR: diff exited with exit code: ${DIFF_STATUS}" >&2
           exit ${DIFF_STATUS}
    esac
    return ${DIFF_STATUS}
}

# Downloads the current in-tree HPKP (HTTP public key pinning) files.
# Runs a simple xpcshell script to generate up-to-date HPKP information.
# Compares the new HPKP output with the old to determine whether we need to update.
function compare_hpkp_files {
    cd "${BASEDIR}"

    HPKP_PRELOAD_SCRIPT_HG="${HGREPO}/raw-file/default/security/manager/tools/${HPKP_PRELOAD_SCRIPT}"
    HPKP_PRELOAD_JSON_HG="${HGREPO}/raw-file/default/security/manager/tools/${HPKP_PRELOAD_JSON}"
    HPKP_DER_TEST_HG="${HGREPO}/raw-file/default/security/manager/ssl/tests/unit/tlsserver/${HPKP_DER_TEST}"
    if [ "${FLATTENED}" == "true" ]; then
        HPKP_PRELOAD_ERRORS_HG="${HGREPO}/raw-file/default/security/manager/ssl/${HPKP_PRELOAD_ERRORS}"
        HPKP_PRELOAD_OUTPUT_HG="${HGREPO}/raw-file/default/security/manager/ssl/${HPKP_PRELOAD_OUTPUT}"
    else
        HPKP_PRELOAD_ERRORS_HG="${HGREPO}/raw-file/default/security/manager/boot/src/${HPKP_PRELOAD_ERRORS}"
        HPKP_PRELOAD_OUTPUT_HG="${HGREPO}/raw-file/default/security/manager/boot/src/${HPKP_PRELOAD_OUTPUT}"
    fi

    # Download everything we need: updater script, existing preload list and errors.
    echo "INFO: Downloading all the necessary pieces to update HPKP..."
    rm -f "${HPKP_PRELOAD_SCRIPT}" "${HPKP_PRELOAD_JSON}" "${HPKP_DER_TEST}" "${HPKP_PRELOAD_OUTPUT}" "${HPKP_PRELOAD_ERRORS}"
    for URL in "${HPKP_PRELOAD_SCRIPT_HG}" "${HPKP_PRELOAD_JSON_HG}" "${HPKP_DER_TEST_HG}" "${HPKP_PRELOAD_OUTPUT_HG}" "${HPKP_PRELOAD_ERRORS_HG}"; do
        echo "INFO: ${WGET} ${URL}"
        ${WGET} ${URL}
        WGET_STATUS=$?
        if [ ${WGET_STATUS} != 0 ]; then
            echo "ERROR: wget exited with a non-zero exit code: ${WGET_STATUS}" >&2
            exit ${WGET_STATUS}
        fi
        if [ ! -f "$(basename "${URL}")" ]; then
            echo "Downloaded file '$(basename "${URL}")' not found in directory '$(pwd)' - this should have been downloaded above from ${URL}." >&2
            exit 51
        fi
    done

    # Run the script to get an updated preload list.
    echo "INFO: Generating new HPKP preload list..."
    cd "${BASEDIR}/${PRODUCT}"
    echo INFO: Running \"LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./xpcshell ${BASEDIR}/${HPKP_PRELOAD_SCRIPT} ${BASEDIR}/${HPKP_PRELOAD_JSON} ${BASEDIR}/${HPKP_DER_TEST} ${BASEDIR}/${PRODUCT}/${HPKP_PRELOAD_OUTPUT}\"
    LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./xpcshell "${BASEDIR}/${HPKP_PRELOAD_SCRIPT}" "${BASEDIR}/${HPKP_PRELOAD_JSON}" "${BASEDIR}/${HPKP_DER_TEST}" "${BASEDIR}/${PRODUCT}/${HPKP_PRELOAD_OUTPUT}" > "${HPKP_PRELOAD_ERRORS}" 2>&1
    XPCSHELL_STATUS=$?
    if [ ${XPCSHELL_STATUS} != 0 ]; then
        cat ${HPKP_PRELOAD_ERRORS}
        echo "ERROR: xpcshell exited with a non-zero exit code: ${XPCSHELL_STATUS}" >&2
        exit 71
    fi

    # The created files should be non-empty.
    echo "INFO: Checking whether new HPKP preload list is valid..."
    if [ ! -s "${HPKP_PRELOAD_ERRORS}" ]; then
        echo "New ${HPKP_PRELOAD_ERRORS} error list is empty. I guess that's good?" >&2
    fi
    if [ ! -s "${HPKP_PRELOAD_OUTPUT}" ]; then
        echo "${HPKP_PRELOAD_OUTPUT} is empty. That's less good." >&2
        exit 52
    fi
    cd "${BASEDIR}"

    # Check for differences
    echo "INFO: diffing old/new HPKP error lists..."
    ${DIFF} "${HPKP_PRELOAD_ERRORS}" "${PRODUCT}/${HPKP_PRELOAD_ERRORS}"
    DIFF_STATUS=$?
    case "${DIFF_STATUS}" in
        0) echo "INFO: New HPKP preload error matches what is in-tree.";;
        1) echo "INFO: New HPKP preload error lists differs from what is in-tree.";;
        *) echo "ERROR: diff exited with exit code: ${DIFF_STATUS}" >&2
           exit ${DIFF_STATUS}
    esac

    echo "INFO: diffing old/new HPKP preload lists..."
    ${DIFF} "${HPKP_PRELOAD_OUTPUT}" "${PRODUCT}/${HPKP_PRELOAD_OUTPUT}"
    DIFF_STATUS=$?
    case "${DIFF_STATUS}" in
        0) echo "INFO: New HPKP preload lists matches what is in-tree.";;
        1) echo "INFO: New HPKP preload lists differs from what is in-tree.";;
        *) echo "ERROR: diff exited with exit code: ${DIFF_STATUS}" >&2
           exit ${DIFF_STATUS}
    esac
    return ${DIFF_STATUS}
}

function is_valid_xml {
   xmlfile=$1
   XMLLINT=`which xmllint 2>/dev/null | head -n1`
   if [ ! -e ${XMLLINT} ]; then
       echo "ERROR: xmllint not found in PATH"
       exit 60
   fi
   ${XMLLINT} --nonet --noout ${xmlfile}
   return $?
}

# Downloads the current in-tree blocklist file.
# Downloads the current blocklist file from AMO.
# Compares the AMO blocklist with the in-tree blocklist to determine whether we need to update.
function compare_blocklist_files {
    BLOCKLIST_URL_AMO="https://blocklist.addons.mozilla.org/blocklist/3/${APP_ID}/${VERSION}/${APP_NAME}/20090105024647/blocklist-sync/en-US/nightly/blocklist-sync/default/default/"
    BLOCKLIST_URL_HG="${HGREPO}/raw-file/default/${APP_DIR}/app/blocklist.xml"

    cd "${BASEDIR}"
    rm -f ${BLOCKLIST_LOCAL_AMO}
    echo "INFO: ${WGET} -O ${BLOCKLIST_LOCAL_AMO} ${BLOCKLIST_URL_AMO}"
    ${WGET} -O ${BLOCKLIST_LOCAL_AMO} ${BLOCKLIST_URL_AMO}
    WGET_STATUS=$?
    if [ ${WGET_STATUS} != 0 ]; then
        echo "ERROR: wget exited with a non-zero exit code: ${WGET_STATUS}"
        exit ${WGET_STATUS}
    fi

    rm -f ${BLOCKLIST_LOCAL_HG}
    echo "INFO: ${WGET} -O ${BLOCKLIST_LOCAL_HG} ${BLOCKLIST_URL_HG}"
    ${WGET} -O ${BLOCKLIST_LOCAL_HG} ${BLOCKLIST_URL_HG}
    WGET_STATUS=$?
    if [ ${WGET_STATUS} != 0 ]; then
        echo "ERROR: wget exited with a non-zero exit code: ${WGET_STATUS}" >&2
        exit ${WGET_STATUS}
    fi

    # The downloaded files should be non-empty and have a valid xml header
    # if they were retrieved properly, and some random HTML garbage if not.
    if ! is_valid_xml ${BLOCKLIST_LOCAL_AMO}; then
        echo "AMO blocklist does not appear to be an XML file. wget error?" >&2
        exit 61
    fi
    if ! is_valid_xml ${BLOCKLIST_LOCAL_HG}; then
        echo "HG blocklist does not appear to be an XML file. wget error?" >&2
        exit 62
    fi

    echo "INFO: diffing in-tree blocklist against the blocklist from AMO..."
    ${DIFF} ${BLOCKLIST_LOCAL_HG} ${BLOCKLIST_LOCAL_AMO} >/dev/null 2>&1
    DIFF_STATUS=$?
    case "${DIFF_STATUS}" in
        0) echo "INFO: AMO blocklist is identical to the one in-tree.";;
        1) echo "INFO: AMO blocklist differs from the one in-tree.";;
        *) echo "ERROR: diff exited with exit code: ${DIFF_STATUS}" >&2
           exit ${DIFF_STATUS}
    esac
    return ${DIFF_STATUS}
}

# Clones an hg repo, using hgtool preferentially.
function clone_repo {
    cd "${BASEDIR}"
    if [ ! -d "${REPODIR}" ]; then
        CLONE_CMD=""
        if [ -f "${HGTOOL}" ]; then
            # Need to pass the default branch here to avoid pollution from buildprops.json
            # when hgtool.py is run in production.
            CLONE_CMD="${HGTOOL} --branch default"
        else
            # Fallback on vanilla hg
            echo "INFO: hgtool.py not found. Falling back to vanilla hg."
            CLONE_CMD="${HG} clone"
        fi
        CLONE_CMD="${CLONE_CMD} ${HGREPO} ${REPODIR}"

        echo "INFO: cloning from hg: ${CLONE_CMD}"
        ${CLONE_CMD}
        CLONE_STATUS=$?
        if [ ${CLONE_STATUS} != 0 ]; then
            echo "ERROR: hg clone exited with a non-zero exit code: ${CLONE_STATUS}" >&2
            exit ${CLONE_STATUS}
        fi
    fi

    echo "INFO: pulling from hg: ${HG} -R ${REPODIR} pull"
    ${HG} -R ${REPODIR} pull
    PULL_STATUS=$?
    if [ ${PULL_STATUS} != 0 ]; then
        echo "ERROR: hg pull exited with a non-zero exit code: ${PULL_STATUS}" >&2
        exit ${PULL_STATUS}
    fi
    echo "INFO: updating to default: ${HG} -R ${REPODIR} update -C default"
    ${HG} -R ${REPODIR} update -C default
    UPDATE_STATUS=$?
    if [ ${UPDATE_STATUS} != 0 ]; then
        echo "ERROR: hg update exited with a non-zero exit code: ${UPDATE_STATUS}" >&2
        exit ${UPDATE_STATUS}
    fi
}

# Copies new HSTS files in place, and commits them.
function commit_hsts_files {
    cd "${BASEDIR}"
    if [ "${FLATTENED}" == "true" ]; then
        cp -f ${PRODUCT}/${HSTS_PRELOAD_ERRORS} ${REPODIR}/security/manager/ssl/
        cp -f ${PRODUCT}/${HSTS_PRELOAD_INC} ${REPODIR}/security/manager/ssl/
    else
        cp -f ${PRODUCT}/${HSTS_PRELOAD_ERRORS} ${REPODIR}/security/manager/boot/src/
	cp -f ${PRODUCT}/${HSTS_PRELOAD_INC} ${REPODIR}/security/manager/boot/src/
    fi
    COMMIT_MESSAGE="No bug, Automated HSTS preload list update from host ${LOCALHOST}"
    if [ ${DONTBUILD} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - (DONTBUILD)"
    fi
    if [ ${CLOSED_TREE} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - CLOSED TREE"
    fi
    if [ ${APPROVAL} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - a=hsts-update"
    fi
    echo "INFO: committing HSTS changes: ${HG} -R ${REPODIR} commit -u \"${HG_SSH_USER}\" -m \"${COMMIT_MESSAGE}\""
    ${HG} -R ${REPODIR} commit -u "${HG_SSH_USER}" -m "${COMMIT_MESSAGE}"
    COMMIT_STATUS=$?
    if [ ${COMMIT_STATUS} == 0 ]; then
        echo "INFO: hg commit succeeded for HSTS files."
    fi
    return ${COMMIT_STATUS}
}

# Copies new HPKP files in place, and commits them.
function commit_hpkp_files {
    cd "${BASEDIR}"
    if [ "${FLATTENED}" == "true" ]; then
        cp -f ${PRODUCT}/${HPKP_PRELOAD_ERRORS} ${REPODIR}/security/manager/ssl/
        cp -f ${PRODUCT}/${HPKP_PRELOAD_OUTPUT} ${REPODIR}/security/manager/ssl/
    else
        cp -f ${PRODUCT}/${HPKP_PRELOAD_ERRORS} ${REPODIR}/security/manager/boot/src/
        cp -f ${PRODUCT}/${HPKP_PRELOAD_OUTPUT} ${REPODIR}/security/manager/boot/src/
    fi
    COMMIT_MESSAGE="No bug, Automated HPKP preload list update from host ${LOCALHOST}"
    if [ ${DONTBUILD} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - (DONTBUILD)"
    fi
    if [ ${CLOSED_TREE} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - CLOSED TREE"
    fi
    if [ ${APPROVAL} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - a=hpkp-update"
    fi
    echo "INFO: committing HPKP changes: ${HG} -R ${REPODIR} commit -u \"${HG_SSH_USER}\" -m \"${COMMIT_MESSAGE}\""
    ${HG} -R ${REPODIR} commit -u "${HG_SSH_USER}" -m "${COMMIT_MESSAGE}"
    COMMIT_STATUS=$?
    if [ ${COMMIT_STATUS} == 0 ]; then
        echo "INFO: hg commit succeeded for HPKP files."
    fi
    return ${COMMIT_STATUS}
}

# Copies new blocklist file in place, and commits it.
function commit_blocklist_files {
    cd "${BASEDIR}"
    cp -f ${BLOCKLIST_LOCAL_AMO} ${REPODIR}/${APP_DIR}/app/blocklist.xml
    COMMIT_MESSAGE="No bug, Automated blocklist update from host ${LOCALHOST}"
    if [ ${DONTBUILD} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - (DONTBUILD)"
    fi
    if [ ${CLOSED_TREE} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - CLOSED TREE"
    fi
    if [ ${APPROVAL} == true ]; then
        COMMIT_MESSAGE="${COMMIT_MESSAGE} - a=blocklist-update"
    fi
    echo "INFO: committing blocklist changes: ${HG} -R ${REPODIR} commit -u \"${HG_SSH_USER}\" -m \"${COMMIT_MESSAGE}\""
    ${HG} -R ${REPODIR} commit -u "${HG_SSH_USER}" -m "${COMMIT_MESSAGE}"
    COMMIT_STATUS=$?
    if [ ${COMMIT_STATUS} == 0 ]; then
        echo "INFO: hg commit succeeded for blocklist files."
    fi
    return ${COMMIT_STATUS}
}

# Push all pending commits to hg.
function push_repo {
    cd "${BASEDIR}"
    echo ${HG} -R ${REPODIR} push -e \"ssh -l ${HG_SSH_USER} -i ${HG_SSH_KEY}\" ${HGPUSHREPO}
    ${HG} -R ${REPODIR} push -e "ssh -l ${HG_SSH_USER} -i ${HG_SSH_KEY}" ${HGPUSHREPO}
    PUSH_STATUS=$?
    if [ ${PUSH_STATUS} != 0 ]; then
        echo "ERROR: hg push exited with exit code: ${PUSH_STATUS}, probably raced another changeset"
        echo ${HG} -R ${REPODIR} rollback
        ${HG} -R ${REPODIR} rollback
        ROLLBACK_STATUS=$?
        if [ ${ROLLBACK_STATUS} != 0 ]; then
            echo "ERROR: hg rollback failed with exit code: ${ROLLBACK_STATUS}" >&2
            echo "This is unrecoverable, removing the local clone to start fresh next time." >&2
            rm -rf ${REPODIR}
            exit ${ROLLBACK_STATUS}
        fi
    else
        echo "INFO: hg push succeeded."
    fi
    return ${PUSH_STATUS}
}

# Main

# Parse our command-line options.
while [ $# -gt 0 ]; do
    case "$1" in
        -h) usage; exit 0;;
        -x) exitcodes; exit 0;;
        -p) PRODUCT="$2"; shift;;
        -b) BRANCH="$2"; shift;;
        -n) DRY_RUN=true;;
        -c) CLOSED_TREE=true;;
        -d) DONTBUILD=true;;
        -a) APPROVAL=true;;
        --hsts) DO_HSTS=true;;
        --hpkp) DO_HPKP=true;;
        --blocklist) DO_BLOCKLIST=true;;
        -u) HG_SSH_USER="$2"; shift;;
        -k) HG_SSH_KEY="$2"; shift;;
        -r) REPODIR="$2"; shift;;
        --use-mozilla-central) USE_MC=true;;
        --use-ftp-builds) USE_TC=false;;
        -*) usage
            exit 11;;
        *)  break;; # terminate while loop
    esac
    shift
done

# Must supply a code branch to work with.
if [ "${BRANCH}" == "" ]; then
    echo "Error: You must specify a branch with -b branchname." >&2
    usage
    exit 12
fi

# Must choose at least one update action.
if [ "$DO_HSTS" == "false" -a "$DO_HPKP" == "false" -a "$DO_BLOCKLIST" == "false" ]; then
    echo "Error: you must specify at least one action from: --hsts, --hpkp, --blocklist" >&2
    usage
    exit 13
fi

# per-product constants
case "${PRODUCT}" in
    thunderbird)
        APP_DIR="mail"
        APP_ID="%7B3550f703-e582-4d05-9a08-453d09bdfdc6%7D"
        APP_NAME="Thunderbird"
        ;;
    firefox)
        APP_DIR="browser"
        APP_ID="%7Bec8030f7-c20a-464f-9b0e-13a3a9e97384%7D"
        APP_NAME="Firefox"
        ;;
    *)
        echo "Error: Invalid product specified"
        usage
        exit 14
        ;;
esac

if [ "${REPODIR}" == "" ]; then
   REPODIR=`basename ${BRANCH}`
fi

HGREPO="https://${HGHOST}/${BRANCH}"
HGPUSHREPO="ssh://${HGHOST}/${BRANCH}"
MCREPO="https://${HGHOST}/mozilla-central"

VERSION=$(get_version ${HGREPO})
MAJOR_VERSION=`echo $VERSION | sed -e 's/\..*//'`
echo "INFO: parsed version is ${VERSION}"
if [ "${USE_MC}" == "true" ]; then
    MCVERSION=$(get_version ${MCREPO})
    echo "INFO: parsed mozilla-central version is ${MCVERSION}"
    MAJOR_VERSION=`echo $MCVERSION | sed -e 's/\..*//'`
fi

BROWSER_ARCHIVE="${PRODUCT}-${VERSION}.en-US.${PLATFORM}.${PLATFORM_EXT}"
TESTS_ARCHIVE="${PRODUCT}-${VERSION}.en-US.${PLATFORM}.common.tests.zip"
if [ "${USE_MC}" == "true" ]; then
    BROWSER_ARCHIVE="${PRODUCT}-${MCVERSION}.en-US.${PLATFORM}.${PLATFORM_EXT}"
    TESTS_ARCHIVE="${PRODUCT}-${MCVERSION}.en-US.${PLATFORM}.common.tests.zip"
fi
# Simple name builds on >=53.0.0
if [ ${MAJOR_VERSION} -ge 53 ] ; then
    BROWSER_ARCHIVE="target.${PLATFORM_EXT}"
    TESTS_ARCHIVE="target.common.tests.zip"
fi

# Try to find hgtool if it hasn't been set.
if [ ! -f "${HGTOOL}" ]; then
    HGTOOL=`which hgtool.py 2>/dev/null | head -n1`
fi

preflight_cleanup
if [ "${DO_HSTS}" == "true" -o "${DO_HPKP}" == "true" ]; then
    if [ "${USE_TC}" == "true" ]; then
        download_shared_artifacts_from_tc
    else
        download_shared_artifacts_from_ftp
    fi
    unpack_artifacts
    run_tooltool
fi
is_flattened
if [ "${DO_HSTS}" == "true" ]; then
    compare_hsts_files
    if [ $? != 0 ]; then
        HSTS_UPDATED=true
    fi
fi
if [ "${DO_HPKP}" == "true" ]; then
    compare_hpkp_files
    if [ $? != 0 ]; then
        HPKP_UPDATED=true
    fi
fi
if [ "${DO_BLOCKLIST}" == "true" ]; then
    compare_blocklist_files
    if [ $? != 0 ]; then
        BLOCKLIST_UPDATED=true
    fi
fi

if [ "${HSTS_UPDATED}" == "false" -a "${HPKP_UPDATED}" == "false" -a "${BLOCKLIST_UPDATED}" == "false" ]; then
    echo "INFO: no updates required. Exiting."
    exit 0
else
    if [ "${DRY_RUN}" == "true" ]; then
        echo "INFO: Updates are available, not updating hg in dry-run mode."
        exit 2
    fi
fi

clone_repo
if [ "${HSTS_UPDATED}" == "true" ]; then
  commit_hsts_files
fi

if [ "${HPKP_UPDATED}" == "true" ]; then
  commit_hpkp_files
fi

if [ "${BLOCKLIST_UPDATED}" == "true" ]; then
  commit_blocklist_files
fi
push_repo
