1#!/bin/sh
2# shellcheck shell=dash
3
4# This is just a little script that can be downloaded from the internet to
5# install rustup. It just does platform detection, downloads the installer
6# and runs it.
7
8# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
9# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
10
11if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then
12    # The version of ksh93 that ships with many illumos systems does not
13    # support the "local" extension.  Print a message rather than fail in
14    # subtle ways later on:
15    echo 'rustup does not work with this ksh93 version; please try bash!' >&2
16    exit 1
17fi
18
19
20set -u
21
22# If RUSTUP_UPDATE_ROOT is unset or empty, default it.
23RUSTUP_UPDATE_ROOT="${RUSTUP_UPDATE_ROOT:-https://static.rust-lang.org/rustup}"
24
25#XXX: If you change anything here, please make the same changes in setup_mode.rs
26usage() {
27    cat 1>&2 <<EOF
28rustup-init 1.24.3 (c1c769109 2021-05-31)
29The installer for rustup
30
31USAGE:
32    rustup-init [FLAGS] [OPTIONS]
33
34FLAGS:
35    -v, --verbose           Enable verbose output
36    -q, --quiet             Disable progress output
37    -y                      Disable confirmation prompt.
38        --no-modify-path    Don't configure the PATH environment variable
39    -h, --help              Prints help information
40    -V, --version           Prints version information
41
42OPTIONS:
43        --default-host <default-host>              Choose a default host triple
44        --default-toolchain <default-toolchain>    Choose a default toolchain to install
45        --default-toolchain none                   Do not install any toolchains
46        --profile [minimal|default|complete]       Choose a profile
47    -c, --component <components>...                Component name to also install
48    -t, --target <targets>...                      Target name to also install
49EOF
50}
51
52main() {
53    downloader --check
54    need_cmd uname
55    need_cmd mktemp
56    need_cmd chmod
57    need_cmd mkdir
58    need_cmd rm
59    need_cmd rmdir
60
61    get_architecture || return 1
62    local _arch="$RETVAL"
63    assert_nz "$_arch" "arch"
64
65    local _ext=""
66    case "$_arch" in
67        *windows*)
68            _ext=".exe"
69            ;;
70    esac
71
72    local _url="${RUSTUP_UPDATE_ROOT}/dist/${_arch}/rustup-init${_ext}"
73
74    local _dir
75    _dir="$(ensure mktemp -d)"
76    local _file="${_dir}/rustup-init${_ext}"
77
78    local _ansi_escapes_are_valid=false
79    if [ -t 2 ]; then
80        if [ "${TERM+set}" = 'set' ]; then
81            case "$TERM" in
82                xterm*|rxvt*|urxvt*|linux*|vt*)
83                    _ansi_escapes_are_valid=true
84                ;;
85            esac
86        fi
87    fi
88
89    # check if we have to use /dev/tty to prompt the user
90    local need_tty=yes
91    for arg in "$@"; do
92        case "$arg" in
93            -h|--help)
94                usage
95                exit 0
96                ;;
97            -y)
98                # user wants to skip the prompt -- we don't need /dev/tty
99                need_tty=no
100                ;;
101            *)
102                ;;
103        esac
104    done
105
106    if $_ansi_escapes_are_valid; then
107        printf "\33[1minfo:\33[0m downloading installer\n" 1>&2
108    else
109        printf '%s\n' 'info: downloading installer' 1>&2
110    fi
111
112    ensure mkdir -p "$_dir"
113    ensure downloader "$_url" "$_file" "$_arch"
114    ensure chmod u+x "$_file"
115    if [ ! -x "$_file" ]; then
116        printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2
117        printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./rustup-init${_ext}." 1>&2
118        exit 1
119    fi
120
121    if [ "$need_tty" = "yes" ]; then
122        # The installer is going to want to ask for confirmation by
123        # reading stdin.  This script was piped into `sh` though and
124        # doesn't have stdin to pass to its children. Instead we're going
125        # to explicitly connect /dev/tty to the installer's stdin.
126        if [ ! -t 1 ]; then
127            err "Unable to run interactively. Run with -y to accept defaults, --help for additional options"
128        fi
129
130        ignore "$_file" "$@" < /dev/tty
131    else
132        ignore "$_file" "$@"
133    fi
134
135    local _retval=$?
136
137    ignore rm "$_file"
138    ignore rmdir "$_dir"
139
140    return "$_retval"
141}
142
143check_proc() {
144    # Check for /proc by looking for the /proc/self/exe link
145    # This is only run on Linux
146    if ! test -L /proc/self/exe ; then
147        err "fatal: Unable to find /proc/self/exe.  Is /proc mounted?  Installation cannot proceed without /proc."
148    fi
149}
150
151get_bitness() {
152    need_cmd head
153    # Architecture detection without dependencies beyond coreutils.
154    # ELF files start out "\x7fELF", and the following byte is
155    #   0x01 for 32-bit and
156    #   0x02 for 64-bit.
157    # The printf builtin on some shells like dash only supports octal
158    # escape sequences, so we use those.
159    local _current_exe_head
160    _current_exe_head=$(head -c 5 /proc/self/exe )
161    if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then
162        echo 32
163    elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then
164        echo 64
165    else
166        err "unknown platform bitness"
167    fi
168}
169
170is_host_amd64_elf() {
171    need_cmd head
172    need_cmd tail
173    # ELF e_machine detection without dependencies beyond coreutils.
174    # Two-byte field at offset 0x12 indicates the CPU,
175    # but we're interested in it being 0x3E to indicate amd64, or not that.
176    local _current_exe_machine
177    _current_exe_machine=$(head -c 19 /proc/self/exe | tail -c 1)
178    [ "$_current_exe_machine" = "$(printf '\076')" ]
179}
180
181get_endianness() {
182    local cputype=$1
183    local suffix_eb=$2
184    local suffix_el=$3
185
186    # detect endianness without od/hexdump, like get_bitness() does.
187    need_cmd head
188    need_cmd tail
189
190    local _current_exe_endianness
191    _current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
192    if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then
193        echo "${cputype}${suffix_el}"
194    elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then
195        echo "${cputype}${suffix_eb}"
196    else
197        err "unknown platform endianness"
198    fi
199}
200
201get_architecture() {
202    local _ostype _cputype _bitness _arch _clibtype
203    _ostype="$(uname -s)"
204    _cputype="$(uname -m)"
205    _clibtype="gnu"
206
207    if [ "$_ostype" = Linux ]; then
208        if [ "$(uname -o)" = Android ]; then
209            _ostype=Android
210        fi
211        if ldd --version 2>&1 | grep -q 'musl'; then
212            _clibtype="musl"
213        fi
214    fi
215
216    if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
217        # Darwin `uname -m` lies
218        if sysctl hw.optional.x86_64 | grep -q ': 1'; then
219            _cputype=x86_64
220        fi
221    fi
222
223    if [ "$_ostype" = SunOS ]; then
224        # Both Solaris and illumos presently announce as "SunOS" in "uname -s"
225        # so use "uname -o" to disambiguate.  We use the full path to the
226        # system uname in case the user has coreutils uname first in PATH,
227        # which has historically sometimes printed the wrong value here.
228        if [ "$(/usr/bin/uname -o)" = illumos ]; then
229            _ostype=illumos
230        fi
231
232        # illumos systems have multi-arch userlands, and "uname -m" reports the
233        # machine hardware name; e.g., "i86pc" on both 32- and 64-bit x86
234        # systems.  Check for the native (widest) instruction set on the
235        # running kernel:
236        if [ "$_cputype" = i86pc ]; then
237            _cputype="$(isainfo -n)"
238        fi
239    fi
240
241    case "$_ostype" in
242
243        Android)
244            _ostype=linux-android
245            ;;
246
247        Linux)
248            check_proc
249            _ostype=unknown-linux-$_clibtype
250            _bitness=$(get_bitness)
251            ;;
252
253        FreeBSD)
254            _ostype=unknown-freebsd
255            ;;
256
257        NetBSD)
258            _ostype=unknown-netbsd
259            ;;
260
261        DragonFly)
262            _ostype=unknown-dragonfly
263            ;;
264
265        Darwin)
266            _ostype=apple-darwin
267            ;;
268
269        illumos)
270            _ostype=unknown-illumos
271            ;;
272
273        MINGW* | MSYS* | CYGWIN*)
274            _ostype=pc-windows-gnu
275            ;;
276
277        *)
278            err "unrecognized OS type: $_ostype"
279            ;;
280
281    esac
282
283    case "$_cputype" in
284
285        i386 | i486 | i686 | i786 | x86)
286            _cputype=i686
287            ;;
288
289        xscale | arm)
290            _cputype=arm
291            if [ "$_ostype" = "linux-android" ]; then
292                _ostype=linux-androideabi
293            fi
294            ;;
295
296        armv6l)
297            _cputype=arm
298            if [ "$_ostype" = "linux-android" ]; then
299                _ostype=linux-androideabi
300            else
301                _ostype="${_ostype}eabihf"
302            fi
303            ;;
304
305        armv7l | armv8l)
306            _cputype=armv7
307            if [ "$_ostype" = "linux-android" ]; then
308                _ostype=linux-androideabi
309            else
310                _ostype="${_ostype}eabihf"
311            fi
312            ;;
313
314        aarch64 | arm64)
315            _cputype=aarch64
316            ;;
317
318        x86_64 | x86-64 | x64 | amd64)
319            _cputype=x86_64
320            ;;
321
322        mips)
323            _cputype=$(get_endianness mips '' el)
324            ;;
325
326        mips64)
327            if [ "$_bitness" -eq 64 ]; then
328                # only n64 ABI is supported for now
329                _ostype="${_ostype}abi64"
330                _cputype=$(get_endianness mips64 '' el)
331            fi
332            ;;
333
334        ppc)
335            _cputype=powerpc
336            ;;
337
338        ppc64)
339            _cputype=powerpc64
340            ;;
341
342        ppc64le)
343            _cputype=powerpc64le
344            ;;
345
346        s390x)
347            _cputype=s390x
348            ;;
349        riscv64)
350            _cputype=riscv64gc
351            ;;
352        *)
353            err "unknown CPU type: $_cputype"
354
355    esac
356
357    # Detect 64-bit linux with 32-bit userland
358    if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then
359        case $_cputype in
360            x86_64)
361                if [ -n "${RUSTUP_CPUTYPE:-}" ]; then
362                    _cputype="$RUSTUP_CPUTYPE"
363                else {
364                    # 32-bit executable for amd64 = x32
365                    if is_host_amd64_elf; then {
366                         echo "This host is running an x32 userland; as it stands, x32 support is poor," 1>&2
367                         echo "and there isn't a native toolchain -- you will have to install" 1>&2
368                         echo "multiarch compatibility with i686 and/or amd64, then select one" 1>&2
369                         echo "by re-running this script with the RUSTUP_CPUTYPE environment variable" 1>&2
370                         echo "set to i686 or x86_64, respectively." 1>&2
371                         echo 1>&2
372                         echo "You will be able to add an x32 target after installation by running" 1>&2
373                         echo "  rustup target add x86_64-unknown-linux-gnux32" 1>&2
374                         exit 1
375                    }; else
376                        _cputype=i686
377                    fi
378                }; fi
379                ;;
380            mips64)
381                _cputype=$(get_endianness mips '' el)
382                ;;
383            powerpc64)
384                _cputype=powerpc
385                ;;
386            aarch64)
387                _cputype=armv7
388                if [ "$_ostype" = "linux-android" ]; then
389                    _ostype=linux-androideabi
390                else
391                    _ostype="${_ostype}eabihf"
392                fi
393                ;;
394            riscv64gc)
395                err "riscv64 with 32-bit userland unsupported"
396                ;;
397        esac
398    fi
399
400    # Detect armv7 but without the CPU features Rust needs in that build,
401    # and fall back to arm.
402    # See https://github.com/rust-lang/rustup.rs/issues/587.
403    if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then
404        if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
405            # At least one processor does not have NEON.
406            _cputype=arm
407        fi
408    fi
409
410    _arch="${_cputype}-${_ostype}"
411
412    RETVAL="$_arch"
413}
414
415say() {
416    printf 'rustup: %s\n' "$1"
417}
418
419err() {
420    say "$1" >&2
421    exit 1
422}
423
424need_cmd() {
425    if ! check_cmd "$1"; then
426        err "need '$1' (command not found)"
427    fi
428}
429
430check_cmd() {
431    command -v "$1" > /dev/null 2>&1
432}
433
434assert_nz() {
435    if [ -z "$1" ]; then err "assert_nz $2"; fi
436}
437
438# Run a command that should never fail. If the command fails execution
439# will immediately terminate with an error showing the failing
440# command.
441ensure() {
442    if ! "$@"; then err "command failed: $*"; fi
443}
444
445# This is just for indicating that commands' results are being
446# intentionally ignored. Usually, because it's being executed
447# as part of error handling.
448ignore() {
449    "$@"
450}
451
452# This wraps curl or wget. Try curl first, if not installed,
453# use wget instead.
454downloader() {
455    local _dld
456    local _ciphersuites
457    local _err
458    local _status
459    if check_cmd curl; then
460        _dld=curl
461    elif check_cmd wget; then
462        _dld=wget
463    else
464        _dld='curl or wget' # to be used in error message of need_cmd
465    fi
466
467    if [ "$1" = --check ]; then
468        need_cmd "$_dld"
469    elif [ "$_dld" = curl ]; then
470        get_ciphersuites_for_curl
471        _ciphersuites="$RETVAL"
472        if [ -n "$_ciphersuites" ]; then
473            _err=$(curl --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1)
474            _status=$?
475        else
476            echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
477            if ! check_help_for "$3" curl --proto --tlsv1.2; then
478                echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
479                _err=$(curl --silent --show-error --fail --location "$1" --output "$2" 2>&1)
480                _status=$?
481            else
482                _err=$(curl --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1)
483                _status=$?
484            fi
485        fi
486        if [ -n "$_err" ]; then
487            echo "$_err" >&2
488            if echo "$_err" | grep -q 404$; then
489                err "installer for platform '$3' not found, this may be unsupported"
490            fi
491        fi
492        return $_status
493    elif [ "$_dld" = wget ]; then
494        get_ciphersuites_for_wget
495        _ciphersuites="$RETVAL"
496        if [ -n "$_ciphersuites" ]; then
497            _err=$(wget --https-only --secure-protocol=TLSv1_2 --ciphers "$_ciphersuites" "$1" -O "$2" 2>&1)
498            _status=$?
499        else
500            echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure"
501            if ! check_help_for "$3" wget --https-only --secure-protocol; then
502                echo "Warning: Not enforcing TLS v1.2, this is potentially less secure"
503                _err=$(wget "$1" -O "$2" 2>&1)
504                _status=$?
505            else
506                _err=$(wget --https-only --secure-protocol=TLSv1_2 "$1" -O "$2" 2>&1)
507                _status=$?
508            fi
509        fi
510        if [ -n "$_err" ]; then
511            echo "$_err" >&2
512            if echo "$_err" | grep -q ' 404 Not Found$'; then
513                err "installer for platform '$3' not found, this may be unsupported"
514            fi
515        fi
516        return $_status
517    else
518        err "Unknown downloader"   # should not reach here
519    fi
520}
521
522check_help_for() {
523    local _arch
524    local _cmd
525    local _arg
526    _arch="$1"
527    shift
528    _cmd="$1"
529    shift
530
531    local _category
532    if "$_cmd" --help | grep -q 'For all options use the manual or "--help all".'; then
533      _category="all"
534    else
535      _category=""
536    fi
537
538    case "$_arch" in
539
540        *darwin*)
541        if check_cmd sw_vers; then
542            case $(sw_vers -productVersion) in
543                10.*)
544                    # If we're running on macOS, older than 10.13, then we always
545                    # fail to find these options to force fallback
546                    if [ "$(sw_vers -productVersion | cut -d. -f2)" -lt 13 ]; then
547                        # Older than 10.13
548                        echo "Warning: Detected macOS platform older than 10.13"
549                        return 1
550                    fi
551                    ;;
552                11.*)
553                    # We assume Big Sur will be OK for now
554                    ;;
555                *)
556                    # Unknown product version, warn and continue
557                    echo "Warning: Detected unknown macOS major version: $(sw_vers -productVersion)"
558                    echo "Warning TLS capabilities detection may fail"
559                    ;;
560            esac
561        fi
562        ;;
563
564    esac
565
566    for _arg in "$@"; do
567        if ! "$_cmd" --help $_category | grep -q -- "$_arg"; then
568            return 1
569        fi
570    done
571
572    true # not strictly needed
573}
574
575# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
576# if support by local tools is detected. Detection currently supports these curl backends:
577# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
578get_ciphersuites_for_curl() {
579    if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
580        # user specified custom cipher suites, assume they know what they're doing
581        RETVAL="$RUSTUP_TLS_CIPHERSUITES"
582        return
583    fi
584
585    local _openssl_syntax="no"
586    local _gnutls_syntax="no"
587    local _backend_supported="yes"
588    if curl -V | grep -q ' OpenSSL/'; then
589        _openssl_syntax="yes"
590    elif curl -V | grep -iq ' LibreSSL/'; then
591        _openssl_syntax="yes"
592    elif curl -V | grep -iq ' BoringSSL/'; then
593        _openssl_syntax="yes"
594    elif curl -V | grep -iq ' GnuTLS/'; then
595        _gnutls_syntax="yes"
596    else
597        _backend_supported="no"
598    fi
599
600    local _args_supported="no"
601    if [ "$_backend_supported" = "yes" ]; then
602        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
603        if check_help_for "notspecified" "curl" "--tlsv1.2" "--ciphers" "--proto"; then
604            _args_supported="yes"
605        fi
606    fi
607
608    local _cs=""
609    if [ "$_args_supported" = "yes" ]; then
610        if [ "$_openssl_syntax" = "yes" ]; then
611            _cs=$(get_strong_ciphersuites_for "openssl")
612        elif [ "$_gnutls_syntax" = "yes" ]; then
613            _cs=$(get_strong_ciphersuites_for "gnutls")
614        fi
615    fi
616
617    RETVAL="$_cs"
618}
619
620# Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites
621# if support by local tools is detected. Detection currently supports these wget backends:
622# GnuTLS and OpenSSL (possibly also LibreSSL and BoringSSL). Return value can be empty.
623get_ciphersuites_for_wget() {
624    if [ -n "${RUSTUP_TLS_CIPHERSUITES-}" ]; then
625        # user specified custom cipher suites, assume they know what they're doing
626        RETVAL="$RUSTUP_TLS_CIPHERSUITES"
627        return
628    fi
629
630    local _cs=""
631    if wget -V | grep -q '\-DHAVE_LIBSSL'; then
632        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
633        if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
634            _cs=$(get_strong_ciphersuites_for "openssl")
635        fi
636    elif wget -V | grep -q '\-DHAVE_LIBGNUTLS'; then
637        # "unspecified" is for arch, allows for possibility old OS using macports, homebrew, etc.
638        if check_help_for "notspecified" "wget" "TLSv1_2" "--ciphers" "--https-only" "--secure-protocol"; then
639            _cs=$(get_strong_ciphersuites_for "gnutls")
640        fi
641    fi
642
643    RETVAL="$_cs"
644}
645
646# Return strong TLS 1.2-1.3 cipher suites in OpenSSL or GnuTLS syntax. TLS 1.2
647# excludes non-ECDHE and non-AEAD cipher suites. DHE is excluded due to bad
648# DH params often found on servers (see RFC 7919). Sequence matches or is
649# similar to Firefox 68 ESR with weak cipher suites disabled via about:config.
650# $1 must be openssl or gnutls.
651get_strong_ciphersuites_for() {
652    if [ "$1" = "openssl" ]; then
653        # OpenSSL is forgiving of unknown values, no problems with TLS 1.3 values on versions that don't support it yet.
654        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"
655    elif [ "$1" = "gnutls" ]; then
656        # GnuTLS isn't forgiving of unknown values, so this may require a GnuTLS version that supports TLS 1.3 even if wget doesn't.
657        # Begin with SECURE128 (and higher) then remove/add to build cipher suites. Produces same 9 cipher suites as OpenSSL but in slightly different order.
658        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"
659    fi
660}
661
662main "$@" || exit 1
663