rust: Update scripts and create tarball management tool. Fixes: 219517953 Change-Id: Ic927568c8836315b5e7b915a8f0fed2efda616b7
diff --git a/fetch-rust-toolchain.sh b/fetch-rust-toolchain.sh new file mode 100755 index 0000000..df059bd --- /dev/null +++ b/fetch-rust-toolchain.sh
@@ -0,0 +1,176 @@ +#!/bin/bash +# +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fetches the rust toolchain from cloud storage + +PUBLIC_ARTIFACTS_PATH="gs://shodan-public-artifacts" +PUBLIC_ARTIFACTS_URL="https://storage.googleapis.com/shodan-public-artifacts" + +function die { + echo "$@" >/dev/stderr + exit 1 +} + +function try { + echo "$@" + "$@" || die "Failed to execute '$@': exited with return code $?" +} + +function in-dir { + local directory="$1"; shift + local exitcode="" + + echo "Entering directory ${directory}" + (cd "$directory" && "$@") + exitcode="$?" + echo "Leaving directory ${directory}" + + if [[ "${exitcode}" != "0" ]]; then + die "Failed to execute '$@': exited with return code ${exitcode}" + fi +} + +function list-tarballs { + if ! hash gsutil 2>/dev/null; then + die "Google cloud SDK is not installed." + fi + + echo "Available tarballs:" + echo + gsutil ls "${PUBLIC_ARTIFACTS_PATH}/rust_toolchain*.tar.xz" +} + +function generate-tarball-name { + local version="$1"; shift + if [[ -z "${version}" ]]; then + die "No version specified." + fi + + echo "rust_toolchain_${version}.tar.xz" +} + +function get-original-name { + local tarball="$1"; shift + cat "${tarball}.sha256sum" |awk '{ print $2 }' +} + +function download-tarball { + local version="$1"; shift + local tarball="$(generate-tarball-name ${version})" + local checksum="${tarball}.sha256sum" + + try wget -O "${ROOTDIR}/out/${tarball}" "${PUBLIC_ARTIFACTS_URL}/${tarball}" + try wget -O "${ROOTDIR}/out/${checksum}" "${PUBLIC_ARTIFACTS_URL}/${checksum}" + + # Workaround the fact that we use the datestamped version of the filename + # at sha256sum creation time. IOW, "latest" is a symbolic name to make + # fetching easier, so we have to rename the tarball to the original name. + # Conveniently, this also allows us to determine which tarball is currently + # set as the latest in storage. + if [[ "${version}" == "latest" ]]; then + local original_name=$(get-original-name "${ROOTDIR}/out/${tarball}") + try mv "${ROOTDIR}/out/${tarball}" "${ROOTDIR}/out/${original_name}" + try mv "${ROOTDIR}/out/${checksum}" "${ROOTDIR}/out/${original_name}.sha256sum" + tarball="${original_name}" + checksum="${original_name}.sha256sum" + fi + + try in-dir "${ROOTDIR}/out" sha256sum -c "${checksum}" + try tar -C "${ROOTDIR}/cache" -xf "${ROOTDIR}/out/${tarball}" +} + +function show-usage { + cat >/dev/stderr <<EOF +Usage: fetch-rust-toolchain.sh <-d|-l> [-v <date|latest>] + +Fetches, verifies, and untars Rust toolchain tarballs from upstram cloud storage +into cache/. + +Options: + -l | --list List available rust tarballs. Note: this requires the + Google Cloud SDK to be installed and logged in. + -d | --download Download a tarball. If -v is not provided, assumes + "latest". + -v <date|latest> | --version <date|latest> + Download either the tarball of the specified version, or + the latest one from upstream storage. If this option is + not specified, defaults to "latest". +EOF +} + +function main { + local usage="Usage: install-rust-toolchain.sh [-d|-l|-v <datestamp>]" + local args=$(getopt -o hdlv: \ + --long help,download,list:,version: \ + -n fetch-rust-toolchain.sh -- "$@") + eval set -- "$args" + + local version="latest" + local mode="" + + while true; do + case "$1" in + -d|--download) + mode="download" + shift + ;; + + -l|--list) + mode="list" + shift + ;; + + -v|--version) + version="$2" + shift + shift + ;; + + --) + shift + break + ;; + + *) + die "${usage}" + ;; + esac + done + + case "${mode}" in + list) + list-tarballs + ;; + + download) + download-tarball "${version}" + ;; + + *) + show-usage + ;; + esac +} + +if [[ "$EUID" == 0 ]]; then + die "This script must NOT be run as root." +fi + +if [[ -z "${ROOTDIR}" || -z "${RUSTDIR}" ]]; then + die "Source build/setup.sh first" +fi + +main "$@"
diff --git a/install-rust-toolchain.sh b/install-rust-toolchain.sh index b524aca..5a0887c 100755 --- a/install-rust-toolchain.sh +++ b/install-rust-toolchain.sh
@@ -1,29 +1,132 @@ -#! /bin/bash +#!/bin/bash +# +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Install the rust toolchain to specified version and variant -if [[ "$#" -ne 3 || $1 == "--help" ]]; then - echo "Usage: install-rust-toolchain.sh <rust dir> <build version|rust-toolchain path> <target>" - exit 1 +RUSTUP="${RUSTDIR}/bin/rustup" + +function die { + echo "$@" >/dev/stderr + exit 1 +} + +function try { + echo "$@" + "$@" || die "Failed to execute '$@': exited with return code $?" +} + +function in-dir { + local directory="$1"; shift + local exitcode="" + + echo "Entering directory ${directory}" + (cd "$directory" && "$@") + exitcode="$?" + echo "Leaving directory ${directory}" + + if [[ "${exitcode}" != "0" ]]; then + die "Failed to execute '$@': exited with return code ${exitcode}" + fi +} + +function main { + local usage="Usage: install-rust-toolchain.sh [-v <version> | -p <project-dir>] <target-to-install>" + local args=$(getopt -o hv:p: \ + --long help,version:,project: \ + -n install-rust-toolchain.sh -- "$@") + eval set -- "$args" + + local version="" + local project_dir="" + + while true; do + case "$1" in + -v|--version) + version="$2" + shift + shift + ;; + + -p|--project) + project_dir="$2" + shift + shift + ;; + + -h|--help) + die "$usage" + ;; + + --) + shift + break + ;; + esac + done + + if [[ ! -z "${version}" && ! -z "${project_dir}" ]]; then + echo "${usage}" + echo + die "-p and -v are mutually exclusive." + fi + + local target="$1"; shift + try mkdir -p "${RUSTDIR}" + + if [[ ! -f "${RUSTUP}" ]]; then + local yesorno="" + echo "========================================================================" + echo "Rustup not found locally -- do you want to install it?" + echo + echo "Please verify that you understand this will fetch binaries" + echo "from potentially untrusted sources. Googlers *must* use" + echo "internally verified builds of the rust compiler toolchains from" + echo "the internal toolchain tarball repository instead. Do not use this" + echo "tool to install Rust locally!" + echo + read -p "Type YES (in all caps) to proceed: " yesorno + + [[ "${yesorno}" != "YES" ]] && die "User did not indicate agreement." + try "${ROOTDIR}/scripts/thirdparty/rustup-install.sh" -y + fi + + if [[ -f "${project_dir}" ]]; then + # The project specifies its own version of the rust toolchain, and the + # user is pointing to its specification files, so let rustup use that to + # install the needed toolchain. + + local project_dir=$(dirname $(realpath "${project_dir}")) + echo "Installing the rust toolchain for project ${project_dir}..." + try in-dir "${project_dir}" "${RUSTUP}" target add "${target}" + elif [[ ! -z "${version}" ]]; then + # We're being asked to install a specific version of the rust toolchain. + + echo "Installing rust toolchain ${version} for target ${target}..." + try "${RUSTUP}" "+${version}" target add "${target}" + else + die "One of -p or -v must be specified." + fi +} + +if [[ "$EUID" == 0 ]]; then + die "This script must NOT be run as root." fi -RUST_DIR=$1 - -BUILD_TOOLCHAIN=$2 -TARGET=$3 - -RUSTUP_BIN="${RUST_DIR}/bin/rustup" - -mkdir -p "${RUST_DIR}" - -if [[ ! -f "${RUSTUP_BIN}" ]]; then - bash -c 'curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path' +if [[ -z "${ROOTDIR}" || -z "${RUSTDIR}" ]]; then + die "Source build/setup.sh first" fi -if [[ -f "${BUILD_TOOLCHAIN}" ]]; then - BUILD_PROJECT=$(dirname $(realpath "${BUILD_TOOLCHAIN}")) - echo "Build rust toolchain specified for project ${BUILD_PROJECT}..." - cd "${BUILD_PROJECT}"; "${RUSTUP_BIN}" target add "${TARGET}" -else - echo "Build specified toolchain ${BUILD_TOOLCHAIN}..." - "${RUSTUP_BIN}" "+${BUILD_TOOLCHAIN}" target add "${TARGET}" -fi +main "$@"
diff --git a/manage-rust-toolchain.sh b/manage-rust-toolchain.sh new file mode 100755 index 0000000..dcc4990 --- /dev/null +++ b/manage-rust-toolchain.sh
@@ -0,0 +1,190 @@ +#!/bin/bash +# +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build a rust toolchain tarball for download use later. + +PUBLIC_ARTIFACTS_PATH="gs://shodan-public-artifacts" + +function die { + echo "$@" >/dev/stderr + exit 1 +} + +function try { + echo "$@" + "$@" || die "Failed to execute '$@': exited with return code $?" +} + +function show-usage { + cat >/dev/stderr <<EOF +Usage: manage-rust-toolchain.sh [-c|-u|-p <date>] + +Creates, uploads, and promotes Rust toolchain tarballs from toolchain installs +on local disk in cache/. + +Options: + -l | --list List available rust tarballs. + -c | --create Create a new toolchain tarball and sha256sum from files in + cache/. + -u | --upload Upload the existing toolchain tarball to Google storage. + Note: requires the Google Cloud SDK to be installed and + logged in to function. + -p <date> | --promote <date> + Promote the given date stamp tarball to latest. +EOF + exit 1 +} + +function generate-tarball-name { + local datestamp=$(date -I) + echo "rust_toolchain_${datestamp}.tar.xz" +} + +function create-tarball { + if [[ ! -d "${RUSTDIR}" ]]; then + die "No rust toolchain installed at ${RUSTDIR}. Please install it first, and try again." + fi + + local tarball="$(generate-tarball-name)" + + if [[ -f "${ROOTDIR}/out/${tarball}" ]]; then + die "Tarball ${tarball} already exists -- cowardly refusing to overwrite it." + fi + + echo "Creating tarball in ${ROOTDIR}/out/${tarball}..." + tar -C "${ROOTDIR}/cache" -c -f - rust_toolchain \ + |xz -T0 -9 \ + > "${ROOTDIR}/out/${tarball}" + + if [[ "$?" != 0 ]]; then + rm -f "${ROOTDIR}/out/${tarball}" + die "Couldn't create tarball." + fi + + echo "Generating sha256sums..." + (cd "${ROOTDIR}/out" && sha256sum "${tarball}") > "${ROOTDIR}/out/${tarball}.sha256sum" +} + +function list-tarballs { + echo "Available tarballs:" + echo + gsutil ls "${PUBLIC_ARTIFACTS_PATH}/rust_toolchain*.tar.xz" +} + +function upload-tarball { + local tarball="$(generate-tarball-name)" + + echo "Uploading tarball..." + try gsutil cp "${ROOTDIR}/out/${tarball}" "${PUBLIC_ARTIFACTS_PATH}/${tarball}" + try gsutil cp "${ROOTDIR}/out/${tarball}.sha256sum" "${PUBLIC_ARTIFACTS_PATH}/${tarball}.sha256sum" +} + +function promote-tarball { + local promote_date="$1"; shift + + echo "Promoting tarball rust_toolchain_${promote_date}.tar.xz to rust_toolchain_latest.tar.xz" + try gsutil cp \ + "${PUBLIC_ARTIFACTS_PATH}/rust_toolchain_${promote_date}.tar.xz" \ + "${PUBLIC_ARTIFACTS_PATH}/rust_toolchain_latest.tar.xz" + try gsutil cp \ + "${PUBLIC_ARTIFACTS_PATH}/rust_toolchain_${promote_date}.tar.xz.sha256sum" \ + "${PUBLIC_ARTIFACTS_PATH}/rust_toolchain_latest.tar.xz.sha256sum" +} + +function main { + local usage="Usage: manage-rust-toolchain.sh [-l|-c|-u|-p <date>]" + local args=$(getopt -o h,l,c,u,p: --long help,list,create,upload,promote: \ + -n manage-rust-toolchain.sh -- "$@") + eval set -- "$args" + + local mode="" + local promote_date="" + + while true; do + case "$1" in + -l|--list) + mode="list" + shift + ;; + + -c|--create) + mode="create-tarball" + shift + ;; + + -u|--upload) + mode="upload" + shift + ;; + + -p|--promote) + mode="promote" + promote_date="$2" + shift + shift + ;; + + -h|--help) + show-usage + ;; + + --) + shift + break + ;; + + *) + die "Unknown option '$1'; maybe try --help?" + ;; + esac + done + + case "${mode}" in + list) + list-tarballs + ;; + + create-tarball) + create-tarball + ;; + + upload) + upload-tarball + ;; + + promote) + promote-tarball "${promote_date}" + ;; + + *) + show-usage + ;; + esac +} + +if [[ "$EUID" == 0 ]]; then + die "This script must NOT be run as root." +fi + +if [[ -z "${ROOTDIR}" || -z "${RUSTDIR}" ]]; then + die "Source build/setup.sh first" +fi + +if ! hash gsutil 2>/dev/null; then + die "This script requires the Google SDK to be installed." +fi + +main "$@"
diff --git a/thirdparty/rustup-install.sh b/thirdparty/rustup-install.sh new file mode 100755 index 0000000..a4a79a2 --- /dev/null +++ b/thirdparty/rustup-install.sh
@@ -0,0 +1,662 @@ +#!/bin/sh +# shellcheck shell=dash + +# This is just a little script that can be downloaded from the internet to +# install rustup. It just does platform detection, downloads the installer +# and runs it. + +# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local` +# extension. Note: Most shells limit `local` to 1 var per line, contra bash. + +if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then + # The version of ksh93 that ships with many illumos systems does not + # support the "local" extension. Print a message rather than fail in + # subtle ways later on: + echo 'rustup does not work with this ksh93 version; please try bash!' >&2 + exit 1 +fi + + +set -u + +# If RUSTUP_UPDATE_ROOT is unset or empty, default it. +RUSTUP_UPDATE_ROOT="${RUSTUP_UPDATE_ROOT:-https://static.rust-lang.org/rustup}" + +#XXX: If you change anything here, please make the same changes in setup_mode.rs +usage() { + cat 1>&2 <<EOF +rustup-init 1.24.3 (c1c769109 2021-05-31) +The installer for rustup + +USAGE: + rustup-init [FLAGS] [OPTIONS] + +FLAGS: + -v, --verbose Enable verbose output + -q, --quiet Disable progress output + -y Disable confirmation prompt. + --no-modify-path Don't configure the PATH environment variable + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + --default-host <default-host> Choose a default host triple + --default-toolchain <default-toolchain> Choose a default toolchain to install + --default-toolchain none Do not install any toolchains + --profile [minimal|default|complete] Choose a profile + -c, --component <components>... Component name to also install + -t, --target <targets>... Target name to also install +EOF +} + +main() { + downloader --check + need_cmd uname + need_cmd mktemp + need_cmd chmod + need_cmd mkdir + need_cmd rm + need_cmd rmdir + + get_architecture || return 1 + local _arch="$RETVAL" + assert_nz "$_arch" "arch" + + local _ext="" + case "$_arch" in + *windows*) + _ext=".exe" + ;; + esac + + local _url="${RUSTUP_UPDATE_ROOT}/dist/${_arch}/rustup-init${_ext}" + + local _dir + _dir="$(ensure mktemp -d)" + local _file="${_dir}/rustup-init${_ext}" + + local _ansi_escapes_are_valid=false + if [ -t 2 ]; then + if [ "${TERM+set}" = 'set' ]; then + case "$TERM" in + xterm*|rxvt*|urxvt*|linux*|vt*) + _ansi_escapes_are_valid=true + ;; + esac + fi + fi + + # check if we have to use /dev/tty to prompt the user + local need_tty=yes + for arg in "$@"; do + case "$arg" in + -h|--help) + usage + exit 0 + ;; + -y) + # user wants to skip the prompt -- we don't need /dev/tty + need_tty=no + ;; + *) + ;; + esac + done + + if $_ansi_escapes_are_valid; then + printf "\33[1minfo:\33[0m downloading installer\n" 1>&2 + else + printf '%s\n' 'info: downloading installer' 1>&2 + fi + + ensure mkdir -p "$_dir" + ensure downloader "$_url" "$_file" "$_arch" + ensure chmod u+x "$_file" + if [ ! -x "$_file" ]; then + printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2 + printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./rustup-init${_ext}." 1>&2 + exit 1 + fi + + if [ "$need_tty" = "yes" ]; then + # The installer is going to want to ask for confirmation by + # reading stdin. This script was piped into `sh` though and + # doesn't have stdin to pass to its children. Instead we're going + # to explicitly connect /dev/tty to the installer's stdin. + if [ ! -t 1 ]; then + err "Unable to run interactively. Run with -y to accept defaults, --help for additional options" + fi + + ignore "$_file" "$@" < /dev/tty + else + ignore "$_file" "$@" + fi + + local _retval=$? + + ignore rm "$_file" + ignore rmdir "$_dir" + + return "$_retval" +} + +check_proc() { + # Check for /proc by looking for the /proc/self/exe link + # This is only run on Linux + if ! test -L /proc/self/exe ; then + err "fatal: Unable to find /proc/self/exe. Is /proc mounted? Installation cannot proceed without /proc." + fi +} + +get_bitness() { + need_cmd head + # Architecture detection without dependencies beyond coreutils. + # ELF files start out "\x7fELF", and the following byte is + # 0x01 for 32-bit and + # 0x02 for 64-bit. + # The printf builtin on some shells like dash only supports octal + # escape sequences, so we use those. + local _current_exe_head + _current_exe_head=$(head -c 5 /proc/self/exe ) + if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then + echo 32 + elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then + echo 64 + else + err "unknown platform bitness" + fi +} + +is_host_amd64_elf() { + need_cmd head + need_cmd tail + # ELF e_machine detection without dependencies beyond coreutils. + # Two-byte field at offset 0x12 indicates the CPU, + # but we're interested in it being 0x3E to indicate amd64, or not that. + local _current_exe_machine + _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1) + [ "$_current_exe_machine" = "$(printf '\076')" ] +} + +get_endianness() { + local cputype=$1 + local suffix_eb=$2 + local suffix_el=$3 + + # detect endianness without od/hexdump, like get_bitness() does. + need_cmd head + need_cmd tail + + local _current_exe_endianness + _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)" + if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then + echo "${cputype}${suffix_el}" + elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then + echo "${cputype}${suffix_eb}" + else + err "unknown platform endianness" + fi +} + +get_architecture() { + local _ostype _cputype _bitness _arch _clibtype + _ostype="$(uname -s)" + _cputype="$(uname -m)" + _clibtype="gnu" + + if [ "$_ostype" = Linux ]; then + if [ "$(uname -o)" = Android ]; then + _ostype=Android + fi + if ldd --version 2>&1 | grep -q 'musl'; then + _clibtype="musl" + fi + fi + + if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then + # Darwin `uname -m` lies + if sysctl hw.optional.x86_64 | grep -q ': 1'; then + _cputype=x86_64 + fi + fi + + if [ "$_ostype" = SunOS ]; then + # Both Solaris and illumos presently announce as "SunOS" in "uname -s" + # so use "uname -o" to disambiguate. We use the full path to the + # system uname in case the user has coreutils uname first in PATH, + # which has historically sometimes printed the wrong value here. + if [ "$(/usr/bin/uname -o)" = illumos ]; then + _ostype=illumos + fi + + # illumos systems have multi-arch userlands, and "uname -m" reports the + # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86 + # systems. Check for the native (widest) instruction set on the + # running kernel: + if [ "$_cputype" = i86pc ]; then + _cputype="$(isainfo -n)" + fi + fi + + case "$_ostype" in + + Android) + _ostype=linux-android + ;; + + Linux) + check_proc + _ostype=unknown-linux-$_clibtype + _bitness=$(get_bitness) + ;; + + FreeBSD) + _ostype=unknown-freebsd + ;; + + NetBSD) + _ostype=unknown-netbsd + ;; + + DragonFly) + _ostype=unknown-dragonfly + ;; + + Darwin) + _ostype=apple-darwin + ;; + + illumos) + _ostype=unknown-illumos + ;; + + MINGW* | MSYS* | CYGWIN*) + _ostype=pc-windows-gnu + ;; + + *) + err "unrecognized OS type: $_ostype" + ;; + + esac + + case "$_cputype" in + + i386 | i486 | i686 | i786 | x86) + _cputype=i686 + ;; + + xscale | arm) + _cputype=arm + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + fi + ;; + + armv6l) + _cputype=arm + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + + armv7l | armv8l) + _cputype=armv7 + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + + aarch64 | arm64) + _cputype=aarch64 + ;; + + x86_64 | x86-64 | x64 | amd64) + _cputype=x86_64 + ;; + + mips) + _cputype=$(get_endianness mips '' el) + ;; + + mips64) + if [ "$_bitness" -eq 64 ]; then + # only n64 ABI is supported for now + _ostype="${_ostype}abi64" + _cputype=$(get_endianness mips64 '' el) + fi + ;; + + ppc) + _cputype=powerpc + ;; + + ppc64) + _cputype=powerpc64 + ;; + + ppc64le) + _cputype=powerpc64le + ;; + + s390x) + _cputype=s390x + ;; + riscv64) + _cputype=riscv64gc + ;; + *) + err "unknown CPU type: $_cputype" + + esac + + # Detect 64-bit linux with 32-bit userland + if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then + case $_cputype in + x86_64) + if [ -n "${RUSTUP_CPUTYPE:-}" ]; then + _cputype="$RUSTUP_CPUTYPE" + else { + # 32-bit executable for amd64 = x32 + if is_host_amd64_elf; then { + echo "This host is running an x32 userland; as it stands, x32 support is poor," 1>&2 + echo "and there isn't a native toolchain -- you will have to install" 1>&2 + echo "multiarch compatibility with i686 and/or amd64, then select one" 1>&2 + echo "by re-running this script with the RUSTUP_CPUTYPE environment variable" 1>&2 + echo "set to i686 or x86_64, respectively." 1>&2 + echo 1>&2 + echo "You will be able to add an x32 target after installation by running" 1>&2 + echo " rustup target add x86_64-unknown-linux-gnux32" 1>&2 + exit 1 + }; else + _cputype=i686 + fi + }; fi + ;; + mips64) + _cputype=$(get_endianness mips '' el) + ;; + powerpc64) + _cputype=powerpc + ;; + aarch64) + _cputype=armv7 + if [ "$_ostype" = "linux-android" ]; then + _ostype=linux-androideabi + else + _ostype="${_ostype}eabihf" + fi + ;; + riscv64gc) + err "riscv64 with 32-bit userland unsupported" + ;; + esac + fi + + # Detect armv7 but without the CPU features Rust needs in that build, + # and fall back to arm. + # See https://github.com/rust-lang/rustup.rs/issues/587. + if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then + if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then + # At least one processor does not have NEON. + _cputype=arm + fi + fi + + _arch="${_cputype}-${_ostype}" + + RETVAL="$_arch" +} + +say() { + printf 'rustup: %s\n' "$1" +} + +err() { + say "$1" >&2 + exit 1 +} + +need_cmd() { + if ! check_cmd "$1"; then + err "need '$1' (command not found)" + fi +} + +check_cmd() { + command -v "$1" > /dev/null 2>&1 +} + +assert_nz() { + if [ -z "$1" ]; then err "assert_nz $2"; fi +} + +# Run a command that should never fail. If the command fails execution +# will immediately terminate with an error showing the failing +# command. +ensure() { + if ! "$@"; then err "command failed: $*"; fi +} + +# This is just for indicating that commands' results are being +# intentionally ignored. Usually, because it's being executed +# as part of error handling. +ignore() { + "$@" +} + +# This wraps curl or wget. Try curl first, if not installed, +# use wget instead. +downloader() { + local _dld + local _ciphersuites + local _err + local _status + if check_cmd curl; then + _dld=curl + elif check_cmd wget; then + _dld=wget + else + _dld='curl or wget' # to be used in error message of need_cmd + fi + + if [ "$1" = --check ]; then + need_cmd "$_dld" + elif [ "$_dld" = curl ]; then + get_ciphersuites_for_curl + _ciphersuites="$RETVAL" + if [ -n "$_ciphersuites" ]; then + _err=$(curl --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1) + _status=$? + else + echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" + if ! check_help_for "$3" curl --proto --tlsv1.2; then + echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" + _err=$(curl --silent --show-error --fail --location "$1" --output "$2" 2>&1) + _status=$? + else + _err=$(curl --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1) + _status=$? + fi + fi + if [ -n "$_err" ]; then + echo "$_err" >&2 + if echo "$_err" | grep -q 404$; then + err "installer for platform '$3' not found, this may be unsupported" + fi + fi + return $_status + elif [ "$_dld" = wget ]; then + get_ciphersuites_for_wget + _ciphersuites="$RETVAL" + if [ -n "$_ciphersuites" ]; then + _err=$(wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" 2>&1) + _status=$? + else + echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" + if ! check_help_for "$3" wget --https-only --secure-protocol; then + echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" + _err=$(wget "$1" -O "$2" 2>&1) + _status=$? + else + _err=$(wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" 2>&1) + _status=$? + fi + fi + if [ -n "$_err" ]; then + echo "$_err" >&2 + if echo "$_err" | grep -q ' 404 Not Found$'; then + err "installer for platform '$3' not found, this may be unsupported" + fi + fi + return $_status + else + err "Unknown downloader" # should not reach here + fi +} + +check_help_for() { + local _arch + local _cmd + local _arg + _arch="$1" + shift + _cmd="$1" + shift + + local _category + if "$_cmd" --help | grep -q 'For all options use the manual or "--help all".'; then + _category="all" + else + _category="" + fi + + case "$_arch" in + + *darwin*) + if check_cmd sw_vers; then + case $(sw_vers -productVersion) in + 10.*) + # If we're running on macOS, older than 10.13, then we always + # fail to find these options to force fallback + if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then + # Older than 10.13 + echo "Warning: Detected macOS platform older than 10.13" + return 1 + fi + ;; + 11.*) + # We assume Big Sur will be OK for now + ;; + *) + # Unknown product version, warn and continue + echo "Warning: Detected unknown macOS major version: $(sw_vers -productVersion)" + echo "Warning TLS capabilities detection may fail" + ;; + esac + fi + ;; + + esac + + for _arg in "$@"; do + if ! "$_cmd" --help $_category | grep -q -- "$_arg"; then + return 1 + fi + done + + true # not strictly needed +} + +# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites +# if support by local tools is detected. Detection currently supports these curl backends: +# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. +get_ciphersuites_for_curl() { + if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then + # user specified custom cipher suites, assume they know what they're doing + RETVAL="$RUSTUP_TLS_CIPHERSUITES" + return + fi + + local _openssl_syntax="no" + local _gnutls_syntax="no" + local _backend_supported="yes" + if curl -V | grep -q ' OpenSSL/'; then + _openssl_syntax="yes" + elif curl -V | grep -iq ' LibreSSL/'; then + _openssl_syntax="yes" + elif curl -V | grep -iq ' BoringSSL/'; then + _openssl_syntax="yes" + elif curl -V | grep -iq ' GnuTLS/'; then + _gnutls_syntax="yes" + else + _backend_supported="no" + fi + + local _args_supported="no" + if [ "$_backend_supported" = "yes" ]; then + # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. + if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then + _args_supported="yes" + fi + fi + + local _cs="" + if [ "$_args_supported" = "yes" ]; then + if [ "$_openssl_syntax" = "yes" ]; then + _cs=$(get_strong_ciphersuites_for "openssl") + elif [ "$_gnutls_syntax" = "yes" ]; then + _cs=$(get_strong_ciphersuites_for "gnutls") + fi + fi + + RETVAL="$_cs" +} + +# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites +# if support by local tools is detected. Detection currently supports these wget backends: +# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty. +get_ciphersuites_for_wget() { + if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then + # user specified custom cipher suites, assume they know what they're doing + RETVAL="$RUSTUP_TLS_CIPHERSUITES" + return + fi + + local _cs="" + if wget -V | grep -q '\-DHAVE_LIBSSL'; then + # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. + if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then + _cs=$(get_strong_ciphersuites_for "openssl") + fi + elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then + # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc. + if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then + _cs=$(get_strong_ciphersuites_for "gnutls") + fi + fi + + RETVAL="$_cs" +} + +# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2 +# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad +# DH params often found on servers (see RFC 7919). Sequence matches or is +# similar to Firefox 68 ESR with weak cipher suites disabled via about:config. +# $1 must be openssl or gnutls. +get_strong_ciphersuites_for() { + if [ "$1" = "openssl" ]; then + # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet. + echo "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" + elif [ "$1" = "gnutls" ]; then + # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't. + # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order. + echo "SECURE128:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-DTLS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+AEAD:+ECDHE-ECDSA:+ECDHE-RSA:+AES-128-GCM:+CHACHA20-POLY1305:+AES-256-GCM" + fi +} + +main "$@" || exit 1