[Tizen] separate PIC and PIE to fix x86_64 build error
[platform/upstream/dotnet/runtime.git] / .dotnet / dotnet-install.sh
1 #!/usr/bin/env bash
2 # Copyright (c) .NET Foundation and contributors. All rights reserved.
3 # Licensed under the MIT license. See LICENSE file in the project root for full license information.
4 #
5
6 # Stop script on NZEC
7 set -e
8 # Stop script if unbound variable found (use ${var:-} if intentional)
9 set -u
10 # By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success
11 # This is causing it to fail
12 set -o pipefail
13
14 # Use in the the functions: eval $invocation
15 invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"'
16
17 # standard output may be used as a return value in the functions
18 # we need a way to write text on the screen in the functions so that
19 # it won't interfere with the return value.
20 # Exposing stream 3 as a pipe to standard output of the script itself
21 exec 3>&1
22
23 # Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors.
24 # See if stdout is a terminal
25 if [ -t 1 ] && command -v tput > /dev/null; then
26     # see if it supports colors
27     ncolors=$(tput colors || echo 0)
28     if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
29         bold="$(tput bold       || echo)"
30         normal="$(tput sgr0     || echo)"
31         black="$(tput setaf 0   || echo)"
32         red="$(tput setaf 1     || echo)"
33         green="$(tput setaf 2   || echo)"
34         yellow="$(tput setaf 3  || echo)"
35         blue="$(tput setaf 4    || echo)"
36         magenta="$(tput setaf 5 || echo)"
37         cyan="$(tput setaf 6    || echo)"
38         white="$(tput setaf 7   || echo)"
39     fi
40 fi
41
42 say_warning() {
43     printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3
44 }
45
46 say_err() {
47     printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2
48 }
49
50 say() {
51     # using stream 3 (defined in the beginning) to not interfere with stdout of functions
52     # which may be used as return value
53     printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3
54 }
55
56 say_verbose() {
57     if [ "$verbose" = true ]; then
58         say "$1"
59     fi
60 }
61
62 # This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets,
63 #   then and only then should the Linux distribution appear in this list.
64 # Adding a Linux distribution to this list does not imply distribution-specific support.
65 get_legacy_os_name_from_platform() {
66     eval $invocation
67
68     platform="$1"
69     case "$platform" in
70         "centos.7")
71             echo "centos"
72             return 0
73             ;;
74         "debian.8")
75             echo "debian"
76             return 0
77             ;;
78         "debian.9")
79             echo "debian.9"
80             return 0
81             ;;
82         "fedora.23")
83             echo "fedora.23"
84             return 0
85             ;;
86         "fedora.24")
87             echo "fedora.24"
88             return 0
89             ;;
90         "fedora.27")
91             echo "fedora.27"
92             return 0
93             ;;
94         "fedora.28")
95             echo "fedora.28"
96             return 0
97             ;;
98         "opensuse.13.2")
99             echo "opensuse.13.2"
100             return 0
101             ;;
102         "opensuse.42.1")
103             echo "opensuse.42.1"
104             return 0
105             ;;
106         "opensuse.42.3")
107             echo "opensuse.42.3"
108             return 0
109             ;;
110         "rhel.7"*)
111             echo "rhel"
112             return 0
113             ;;
114         "ubuntu.14.04")
115             echo "ubuntu"
116             return 0
117             ;;
118         "ubuntu.16.04")
119             echo "ubuntu.16.04"
120             return 0
121             ;;
122         "ubuntu.16.10")
123             echo "ubuntu.16.10"
124             return 0
125             ;;
126         "ubuntu.18.04")
127             echo "ubuntu.18.04"
128             return 0
129             ;;
130         "alpine.3.4.3")
131             echo "alpine"
132             return 0
133             ;;
134     esac
135     return 1
136 }
137
138 get_legacy_os_name() {
139     eval $invocation
140
141     local uname=$(uname)
142     if [ "$uname" = "Darwin" ]; then
143         echo "osx"
144         return 0
145     elif [ -n "$runtime_id" ]; then
146         echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}")
147         return 0
148     else
149         if [ -e /etc/os-release ]; then
150             . /etc/os-release
151             os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "")
152             if [ -n "$os" ]; then
153                 echo "$os"
154                 return 0
155             fi
156         fi
157     fi
158
159     say_verbose "Distribution specific OS name and version could not be detected: UName = $uname"
160     return 1
161 }
162
163 get_linux_platform_name() {
164     eval $invocation
165
166     if [ -n "$runtime_id" ]; then
167         echo "${runtime_id%-*}"
168         return 0
169     else
170         if [ -e /etc/os-release ]; then
171             . /etc/os-release
172             echo "$ID${VERSION_ID:+.${VERSION_ID}}"
173             return 0
174         elif [ -e /etc/redhat-release ]; then
175             local redhatRelease=$(</etc/redhat-release)
176             if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux "*" release 6."* ]]; then
177                 echo "rhel.6"
178                 return 0
179             fi
180         fi
181     fi
182
183     say_verbose "Linux specific platform name and version could not be detected: UName = $uname"
184     return 1
185 }
186
187 is_musl_based_distro() {
188     (ldd --version 2>&1 || true) | grep -q musl
189 }
190
191 get_current_os_name() {
192     eval $invocation
193
194     local uname=$(uname)
195     if [ "$uname" = "Darwin" ]; then
196         echo "osx"
197         return 0
198     elif [ "$uname" = "FreeBSD" ]; then
199         echo "freebsd"
200         return 0
201     elif [ "$uname" = "Linux" ]; then
202         local linux_platform_name=""
203         linux_platform_name="$(get_linux_platform_name)" || true
204
205         if [ "$linux_platform_name" = "rhel.6" ]; then
206             echo $linux_platform_name
207             return 0
208         elif is_musl_based_distro; then
209             echo "linux-musl"
210             return 0
211         elif [ "$linux_platform_name" = "linux-musl" ]; then
212             echo "linux-musl"
213             return 0
214         else
215             echo "linux"
216             return 0
217         fi
218     fi
219
220     say_err "OS name could not be detected: UName = $uname"
221     return 1
222 }
223
224 machine_has() {
225     eval $invocation
226
227     command -v "$1" > /dev/null 2>&1
228     return $?
229 }
230
231 check_min_reqs() {
232     local hasMinimum=false
233     if machine_has "curl"; then
234         hasMinimum=true
235     elif machine_has "wget"; then
236         hasMinimum=true
237     fi
238
239     if [ "$hasMinimum" = "false" ]; then
240         say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed."
241         return 1
242     fi
243     return 0
244 }
245
246 # args:
247 # input - $1
248 to_lowercase() {
249     #eval $invocation
250
251     echo "$1" | tr '[:upper:]' '[:lower:]'
252     return 0
253 }
254
255 # args:
256 # input - $1
257 remove_trailing_slash() {
258     #eval $invocation
259
260     local input="${1:-}"
261     echo "${input%/}"
262     return 0
263 }
264
265 # args:
266 # input - $1
267 remove_beginning_slash() {
268     #eval $invocation
269
270     local input="${1:-}"
271     echo "${input#/}"
272     return 0
273 }
274
275 # args:
276 # root_path - $1
277 # child_path - $2 - this parameter can be empty
278 combine_paths() {
279     eval $invocation
280
281     # TODO: Consider making it work with any number of paths. For now:
282     if [ ! -z "${3:-}" ]; then
283         say_err "combine_paths: Function takes two parameters."
284         return 1
285     fi
286
287     local root_path="$(remove_trailing_slash "$1")"
288     local child_path="$(remove_beginning_slash "${2:-}")"
289     say_verbose "combine_paths: root_path=$root_path"
290     say_verbose "combine_paths: child_path=$child_path"
291     echo "$root_path/$child_path"
292     return 0
293 }
294
295 get_machine_architecture() {
296     eval $invocation
297
298     if command -v uname > /dev/null; then
299         CPUName=$(uname -m)
300         case $CPUName in
301         armv*l)
302             echo "arm"
303             return 0
304             ;;
305         aarch64|arm64)
306             echo "arm64"
307             return 0
308             ;;
309         s390x)
310             echo "s390x"
311             return 0
312             ;;
313         ppc64le)
314             echo "ppc64le"
315             return 0
316             ;;
317         loongarch64)
318             echo "loongarch64"
319             return 0
320             ;;
321         esac
322     fi
323
324     # Always default to 'x64'
325     echo "x64"
326     return 0
327 }
328
329 # args:
330 # architecture - $1
331 get_normalized_architecture_from_architecture() {
332     eval $invocation
333
334     local architecture="$(to_lowercase "$1")"
335
336     if [[ $architecture == \<auto\> ]]; then
337         echo "$(get_machine_architecture)"
338         return 0
339     fi
340
341     case "$architecture" in
342         amd64|x64)
343             echo "x64"
344             return 0
345             ;;
346         arm)
347             echo "arm"
348             return 0
349             ;;
350         arm64)
351             echo "arm64"
352             return 0
353             ;;
354         s390x)
355             echo "s390x"
356             return 0
357             ;;
358         ppc64le)
359             echo "ppc64le"
360             return 0
361             ;;
362         loongarch64)
363             echo "loongarch64"
364             return 0
365             ;;
366     esac
367
368     say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
369     return 1
370 }
371
372 # args:
373 # version - $1
374 # channel - $2
375 # architecture - $3
376 get_normalized_architecture_for_specific_sdk_version() {
377     eval $invocation
378
379     local is_version_support_arm64="$(is_arm64_supported "$1")"
380     local is_channel_support_arm64="$(is_arm64_supported "$2")"
381     local architecture="$3";
382     local osname="$(get_current_os_name)"
383
384     if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then
385         #check if rosetta is installed
386         if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then 
387             say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." 
388             echo "x64"
389             return 0;
390         else
391             say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform"
392             return 1
393         fi
394     fi
395
396     echo "$architecture"
397     return 0
398 }
399
400 # args:
401 # version or channel - $1
402 is_arm64_supported() {
403     #any channel or version that starts with the specified versions
404     case "$1" in
405         ( "1"* | "2"* | "3"*  | "4"* | "5"*) 
406             echo false
407             return 0
408     esac
409
410     echo true
411     return 0
412 }
413
414 # args:
415 # user_defined_os - $1
416 get_normalized_os() {
417     eval $invocation
418
419     local osname="$(to_lowercase "$1")"
420     if [ ! -z "$osname" ]; then
421         case "$osname" in
422             osx | freebsd | rhel.6 | linux-musl | linux)
423                 echo "$osname"
424                 return 0
425                 ;;
426             macos)
427                 osname='osx'
428                 echo "$osname"
429                 return 0
430                 ;;
431             *)
432                 say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
433                 return 1
434                 ;;
435         esac
436     else
437         osname="$(get_current_os_name)" || return 1
438     fi
439     echo "$osname"
440     return 0
441 }
442
443 # args:
444 # quality - $1
445 get_normalized_quality() {
446     eval $invocation
447
448     local quality="$(to_lowercase "$1")"
449     if [ ! -z "$quality" ]; then
450         case "$quality" in
451             daily | signed | validated | preview)
452                 echo "$quality"
453                 return 0
454                 ;;
455             ga)
456                 #ga quality is available without specifying quality, so normalizing it to empty
457                 return 0
458                 ;;
459             *)
460                 say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, signed, validated, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
461                 return 1
462                 ;;
463         esac
464     fi
465     return 0
466 }
467
468 # args:
469 # channel - $1
470 get_normalized_channel() {
471     eval $invocation
472
473     local channel="$(to_lowercase "$1")"
474
475     if [[ $channel == current ]]; then
476         say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.'
477     fi
478
479     if [[ $channel == release/* ]]; then
480         say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.';
481     fi
482
483     if [ ! -z "$channel" ]; then
484         case "$channel" in
485             lts)
486                 echo "LTS"
487                 return 0
488                 ;;
489             sts)
490                 echo "STS"
491                 return 0
492                 ;;
493             current)
494                 echo "STS"
495                 return 0
496                 ;;
497             *)
498                 echo "$channel"
499                 return 0
500                 ;;
501         esac
502     fi
503
504     return 0
505 }
506
507 # args:
508 # runtime - $1
509 get_normalized_product() {
510     eval $invocation
511
512     local product=""
513     local runtime="$(to_lowercase "$1")"
514     if [[ "$runtime" == "dotnet" ]]; then
515         product="dotnet-runtime"
516     elif [[ "$runtime" == "aspnetcore" ]]; then
517         product="aspnetcore-runtime"
518     elif [ -z "$runtime" ]; then
519         product="dotnet-sdk"
520     fi
521     echo "$product"
522     return 0
523 }
524
525 # The version text returned from the feeds is a 1-line or 2-line string:
526 # For the SDK and the dotnet runtime (2 lines):
527 # Line 1: # commit_hash
528 # Line 2: # 4-part version
529 # For the aspnetcore runtime (1 line):
530 # Line 1: # 4-part version
531
532 # args:
533 # version_text - stdin
534 get_version_from_latestversion_file_content() {
535     eval $invocation
536
537     cat | tail -n 1 | sed 's/\r$//'
538     return 0
539 }
540
541 # args:
542 # install_root - $1
543 # relative_path_to_package - $2
544 # specific_version - $3
545 is_dotnet_package_installed() {
546     eval $invocation
547
548     local install_root="$1"
549     local relative_path_to_package="$2"
550     local specific_version="${3//[$'\t\r\n']}"
551
552     local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")"
553     say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path"
554
555     if [ -d "$dotnet_package_path" ]; then
556         return 0
557     else
558         return 1
559     fi
560 }
561
562 # args:
563 # downloaded file - $1
564 # remote_file_size - $2
565 validate_remote_local_file_sizes() 
566 {
567     eval $invocation
568
569     local downloaded_file="$1"
570     local remote_file_size="$2"
571     local file_size=''
572
573     if [[ "$OSTYPE" == "linux-gnu"* ]]; then
574         file_size="$(stat -c '%s' "$downloaded_file")"
575     elif [[ "$OSTYPE" == "darwin"* ]]; then
576         # hardcode in order to avoid conflicts with GNU stat
577         file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")"
578     fi  
579     
580     if [ -n "$file_size" ]; then
581         say "Downloaded file size is $file_size bytes."
582
583         if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then
584             if [ "$remote_file_size" -ne "$file_size" ]; then
585                 say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted."
586             else
587                 say "The remote and local file sizes are equal."
588             fi
589         fi
590         
591     else
592         say "Either downloaded or local package size can not be measured. One of them may be corrupted."      
593     fi 
594 }
595
596 # args:
597 # azure_feed - $1
598 # channel - $2
599 # normalized_architecture - $3
600 get_version_from_latestversion_file() {
601     eval $invocation
602
603     local azure_feed="$1"
604     local channel="$2"
605     local normalized_architecture="$3"
606
607     local version_file_url=null
608     if [[ "$runtime" == "dotnet" ]]; then
609         version_file_url="$azure_feed/Runtime/$channel/latest.version"
610     elif [[ "$runtime" == "aspnetcore" ]]; then
611         version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version"
612     elif [ -z "$runtime" ]; then
613          version_file_url="$azure_feed/Sdk/$channel/latest.version"
614     else
615         say_err "Invalid value for \$runtime"
616         return 1
617     fi
618     say_verbose "get_version_from_latestversion_file: latest url: $version_file_url"
619
620     download "$version_file_url" || return $?
621     return 0
622 }
623
624 # args:
625 # json_file - $1
626 parse_globaljson_file_for_version() {
627     eval $invocation
628
629     local json_file="$1"
630     if [ ! -f "$json_file" ]; then
631         say_err "Unable to find \`$json_file\`"
632         return 1
633     fi
634
635     sdk_section=$(cat $json_file | tr -d "\r" | awk '/"sdk"/,/}/')
636     if [ -z "$sdk_section" ]; then
637         say_err "Unable to parse the SDK node in \`$json_file\`"
638         return 1
639     fi
640
641     sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}')
642     sdk_list=${sdk_list//[\" ]/}
643     sdk_list=${sdk_list//,/$'\n'}
644
645     local version_info=""
646     while read -r line; do
647       IFS=:
648       while read -r key value; do
649         if [[ "$key" == "version" ]]; then
650           version_info=$value
651         fi
652       done <<< "$line"
653     done <<< "$sdk_list"
654     if [ -z "$version_info" ]; then
655         say_err "Unable to find the SDK:version node in \`$json_file\`"
656         return 1
657     fi
658
659     unset IFS;
660     echo "$version_info"
661     return 0
662 }
663
664 # args:
665 # azure_feed - $1
666 # channel - $2
667 # normalized_architecture - $3
668 # version - $4
669 # json_file - $5
670 get_specific_version_from_version() {
671     eval $invocation
672
673     local azure_feed="$1"
674     local channel="$2"
675     local normalized_architecture="$3"
676     local version="$(to_lowercase "$4")"
677     local json_file="$5"
678
679     if [ -z "$json_file" ]; then
680         if [[ "$version" == "latest" ]]; then
681             local version_info
682             version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1
683             say_verbose "get_specific_version_from_version: version_info=$version_info"
684             echo "$version_info" | get_version_from_latestversion_file_content
685             return 0
686         else
687             echo "$version"
688             return 0
689         fi
690     else
691         local version_info
692         version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1
693         echo "$version_info"
694         return 0
695     fi
696 }
697
698 # args:
699 # azure_feed - $1
700 # channel - $2
701 # normalized_architecture - $3
702 # specific_version - $4
703 # normalized_os - $5
704 construct_download_link() {
705     eval $invocation
706
707     local azure_feed="$1"
708     local channel="$2"
709     local normalized_architecture="$3"
710     local specific_version="${4//[$'\t\r\n']}"
711     local specific_product_version="$(get_specific_product_version "$1" "$4")"
712     local osname="$5"
713
714     local download_link=null
715     if [[ "$runtime" == "dotnet" ]]; then
716         download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
717     elif [[ "$runtime" == "aspnetcore" ]]; then
718         download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
719     elif [ -z "$runtime" ]; then
720         download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz"
721     else
722         return 1
723     fi
724
725     echo "$download_link"
726     return 0
727 }
728
729 # args:
730 # azure_feed - $1
731 # specific_version - $2
732 # download link - $3 (optional)
733 get_specific_product_version() {
734     # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents
735     # to resolve the version of what's in the folder, superseding the specified version.
736     # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link
737     eval $invocation
738
739     local azure_feed="$1"
740     local specific_version="${2//[$'\t\r\n']}"
741     local package_download_link=""
742     if [ $# -gt 2  ]; then
743         local package_download_link="$3"
744     fi
745     local specific_product_version=null
746
747     # Try to get the version number, using the productVersion.txt file located next to the installer file.
748     local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link")
749         $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link"))
750
751     for download_link in "${download_links[@]}"
752     do
753         say_verbose "Checking for the existence of $download_link"
754
755         if machine_has "curl"
756         then
757             if ! specific_product_version=$(curl -s --fail "${download_link}${feed_credential}" 2>&1); then
758                 continue
759             else
760                 echo "${specific_product_version//[$'\t\r\n']}"
761                 return 0
762             fi
763
764         elif machine_has "wget"
765         then
766             specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1)
767             if [ $? = 0 ]; then
768                 echo "${specific_product_version//[$'\t\r\n']}"
769                 return 0
770             fi
771         fi
772     done
773     
774     # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number.
775     say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead."
776     specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")"
777     echo "${specific_product_version//[$'\t\r\n']}"
778     return 0
779 }
780
781 # args:
782 # azure_feed - $1
783 # specific_version - $2
784 # is_flattened - $3
785 # download link - $4 (optional)
786 get_specific_product_version_url() {
787     eval $invocation
788
789     local azure_feed="$1"
790     local specific_version="$2"
791     local is_flattened="$3"
792     local package_download_link=""
793     if [ $# -gt 3  ]; then
794         local package_download_link="$4"
795     fi
796
797     local pvFileName="productVersion.txt"
798     if [ "$is_flattened" = true ]; then
799         if [ -z "$runtime" ]; then
800             pvFileName="sdk-productVersion.txt"
801         elif [[ "$runtime" == "dotnet" ]]; then
802             pvFileName="runtime-productVersion.txt"
803         else
804             pvFileName="$runtime-productVersion.txt"
805         fi
806     fi
807
808     local download_link=null
809
810     if [ -z "$package_download_link" ]; then
811         if [[ "$runtime" == "dotnet" ]]; then
812             download_link="$azure_feed/Runtime/$specific_version/${pvFileName}"
813         elif [[ "$runtime" == "aspnetcore" ]]; then
814             download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}"
815         elif [ -z "$runtime" ]; then
816             download_link="$azure_feed/Sdk/$specific_version/${pvFileName}"
817         else
818             return 1
819         fi
820     else
821         download_link="${package_download_link%/*}/${pvFileName}"
822     fi
823
824     say_verbose "Constructed productVersion link: $download_link"
825     echo "$download_link"
826     return 0
827 }
828
829 # args:
830 # download link - $1
831 # specific version - $2
832 get_product_specific_version_from_download_link()
833 {
834     eval $invocation
835
836     local download_link="$1"
837     local specific_version="$2"
838     local specific_product_version="" 
839
840     if [ -z "$download_link" ]; then
841         echo "$specific_version"
842         return 0
843     fi
844
845     #get filename
846     filename="${download_link##*/}"
847
848     #product specific version follows the product name
849     #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404
850     IFS='-'
851     read -ra filename_elems <<< "$filename"
852     count=${#filename_elems[@]}
853     if [[ "$count" -gt 2 ]]; then
854         specific_product_version="${filename_elems[2]}"
855     else
856         specific_product_version=$specific_version
857     fi
858     unset IFS;
859     echo "$specific_product_version"
860     return 0
861 }
862
863 # args:
864 # azure_feed - $1
865 # channel - $2
866 # normalized_architecture - $3
867 # specific_version - $4
868 construct_legacy_download_link() {
869     eval $invocation
870
871     local azure_feed="$1"
872     local channel="$2"
873     local normalized_architecture="$3"
874     local specific_version="${4//[$'\t\r\n']}"
875
876     local distro_specific_osname
877     distro_specific_osname="$(get_legacy_os_name)" || return 1
878
879     local legacy_download_link=null
880     if [[ "$runtime" == "dotnet" ]]; then
881         legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
882     elif [ -z "$runtime" ]; then
883         legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
884     else
885         return 1
886     fi
887
888     echo "$legacy_download_link"
889     return 0
890 }
891
892 get_user_install_path() {
893     eval $invocation
894
895     if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then
896         echo "$DOTNET_INSTALL_DIR"
897     else
898         echo "$HOME/.dotnet"
899     fi
900     return 0
901 }
902
903 # args:
904 # install_dir - $1
905 resolve_installation_path() {
906     eval $invocation
907
908     local install_dir=$1
909     if [ "$install_dir" = "<auto>" ]; then
910         local user_install_path="$(get_user_install_path)"
911         say_verbose "resolve_installation_path: user_install_path=$user_install_path"
912         echo "$user_install_path"
913         return 0
914     fi
915
916     echo "$install_dir"
917     return 0
918 }
919
920 # args:
921 # relative_or_absolute_path - $1
922 get_absolute_path() {
923     eval $invocation
924
925     local relative_or_absolute_path=$1
926     echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")"
927     return 0
928 }
929
930 # args:
931 # input_files - stdin
932 # root_path - $1
933 # out_path - $2
934 # override - $3
935 copy_files_or_dirs_from_list() {
936     eval $invocation
937
938     local root_path="$(remove_trailing_slash "$1")"
939     local out_path="$(remove_trailing_slash "$2")"
940     local override="$3"
941     local osname="$(get_current_os_name)"
942     local override_switch=$(
943         if [ "$override" = false ]; then
944             if [ "$osname" = "linux-musl" ]; then
945                 printf -- "-u";
946             else
947                 printf -- "-n";
948             fi
949         fi)
950
951     cat | uniq | while read -r file_path; do
952         local path="$(remove_beginning_slash "${file_path#$root_path}")"
953         local target="$out_path/$path"
954         if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then
955             mkdir -p "$out_path/$(dirname "$path")"
956             if [ -d "$target" ]; then
957                 rm -rf "$target"
958             fi
959             cp -R $override_switch "$root_path/$path" "$target"
960         fi
961     done
962 }
963
964 # args:
965 # zip_uri - $1
966 get_remote_file_size() {
967     local zip_uri="$1"
968
969     if machine_has "curl"; then
970         file_size=$(curl -sI  "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }')
971     elif machine_has "wget"; then
972         file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }')
973     else
974         say "Neither curl nor wget is available on this system."
975         return
976     fi
977
978     if [ -n "$file_size" ]; then
979         say "Remote file $zip_uri size is $file_size bytes."
980         echo "$file_size"
981     else
982         say_verbose "Content-Length header was not extracted for $zip_uri."
983         echo ""
984     fi
985 }
986
987 # args:
988 # zip_path - $1
989 # out_path - $2
990 # remote_file_size - $3
991 extract_dotnet_package() {
992     eval $invocation
993
994     local zip_path="$1"
995     local out_path="$2"
996     local remote_file_size="$3"
997
998     local temp_out_path="$(mktemp -d "$temporary_file_template")"
999
1000     local failed=false
1001     tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true
1002
1003     local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/'
1004     find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false
1005     find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
1006     
1007     validate_remote_local_file_sizes "$zip_path" "$remote_file_size"
1008     
1009     rm -rf "$temp_out_path"
1010     if [ -z ${keep_zip+x} ]; then
1011         rm -f "$zip_path" && say_verbose "Temporary zip file $zip_path was removed"
1012     fi
1013
1014     if [ "$failed" = true ]; then
1015         say_err "Extraction failed"
1016         return 1
1017     fi
1018     return 0
1019 }
1020
1021 # args:
1022 # remote_path - $1
1023 # disable_feed_credential - $2
1024 get_http_header()
1025 {
1026     eval $invocation
1027     local remote_path="$1"
1028     local disable_feed_credential="$2"
1029
1030     local failed=false
1031     local response
1032     if machine_has "curl"; then
1033         get_http_header_curl $remote_path $disable_feed_credential || failed=true
1034     elif machine_has "wget"; then
1035         get_http_header_wget $remote_path $disable_feed_credential || failed=true
1036     else
1037         failed=true
1038     fi
1039     if [ "$failed" = true ]; then
1040         say_verbose "Failed to get HTTP header: '$remote_path'."
1041         return 1
1042     fi
1043     return 0
1044 }
1045
1046 # args:
1047 # remote_path - $1
1048 # disable_feed_credential - $2
1049 get_http_header_curl() {
1050     eval $invocation
1051     local remote_path="$1"
1052     local disable_feed_credential="$2"
1053
1054     remote_path_with_credential="$remote_path"
1055     if [ "$disable_feed_credential" = false ]; then
1056         remote_path_with_credential+="$feed_credential"
1057     fi
1058
1059     curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 "
1060     curl $curl_options "$remote_path_with_credential" 2>&1 || return 1
1061     return 0
1062 }
1063
1064 # args:
1065 # remote_path - $1
1066 # disable_feed_credential - $2
1067 get_http_header_wget() {
1068     eval $invocation
1069     local remote_path="$1"
1070     local disable_feed_credential="$2"
1071     local wget_options="-q -S --spider --tries 5 "
1072
1073     local wget_options_extra=''
1074
1075     # Test for options that aren't supported on all wget implementations.
1076     if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
1077         wget_options_extra="--waitretry 2 --connect-timeout 15 "
1078     else
1079         say "wget extra options are unavailable for this environment"
1080     fi
1081
1082     remote_path_with_credential="$remote_path"
1083     if [ "$disable_feed_credential" = false ]; then
1084         remote_path_with_credential+="$feed_credential"
1085     fi
1086
1087     wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1
1088
1089     return $?
1090 }
1091
1092 # args:
1093 # remote_path - $1
1094 # [out_path] - $2 - stdout if not provided
1095 download() {
1096     eval $invocation
1097
1098     local remote_path="$1"
1099     local out_path="${2:-}"
1100
1101     if [[ "$remote_path" != "http"* ]]; then
1102         cp "$remote_path" "$out_path"
1103         return $?
1104     fi
1105
1106     local failed=false
1107     local attempts=0
1108     while [ $attempts -lt 3 ]; do
1109         attempts=$((attempts+1))
1110         failed=false
1111         if machine_has "curl"; then
1112             downloadcurl "$remote_path" "$out_path" || failed=true
1113         elif machine_has "wget"; then
1114             downloadwget "$remote_path" "$out_path" || failed=true
1115         else
1116             say_err "Missing dependency: neither curl nor wget was found."
1117             exit 1
1118         fi
1119
1120         if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ ! -z $http_code ] && [ $http_code = "404" ]; }; then
1121             break
1122         fi
1123
1124         say "Download attempt #$attempts has failed: $http_code $download_error_msg"
1125         say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds."
1126         sleep $((attempts*10))
1127     done
1128
1129     if [ "$failed" = true ]; then
1130         say_verbose "Download failed: $remote_path"
1131         return 1
1132     fi
1133     return 0
1134 }
1135
1136 # Updates global variables $http_code and $download_error_msg
1137 downloadcurl() {
1138     eval $invocation
1139     unset http_code
1140     unset download_error_msg
1141     local remote_path="$1"
1142     local out_path="${2:-}"
1143     # Append feed_credential as late as possible before calling curl to avoid logging feed_credential
1144     # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output.
1145     local remote_path_with_credential="${remote_path}${feed_credential}"
1146     local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs "
1147     local curl_exit_code=0;
1148     if [ -z "$out_path" ]; then
1149         curl $curl_options "$remote_path_with_credential" 2>&1
1150         curl_exit_code=$?
1151     else
1152         curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1
1153         curl_exit_code=$?
1154     fi
1155     
1156     if [ $curl_exit_code -gt 0 ]; then
1157         download_error_msg="Unable to download $remote_path."
1158         # Check for curl timeout codes
1159         if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then
1160             download_error_msg+=" Failed to reach the server: connection timeout."
1161         else
1162             local disable_feed_credential=false
1163             local response=$(get_http_header_curl $remote_path $disable_feed_credential)
1164             http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 )
1165             if  [[ ! -z $http_code && $http_code != 2* ]]; then
1166                 download_error_msg+=" Returned HTTP status code: $http_code."
1167             fi
1168         fi
1169         say_verbose "$download_error_msg"
1170         return 1
1171     fi
1172     return 0
1173 }
1174
1175
1176 # Updates global variables $http_code and $download_error_msg
1177 downloadwget() {
1178     eval $invocation
1179     unset http_code
1180     unset download_error_msg
1181     local remote_path="$1"
1182     local out_path="${2:-}"
1183     # Append feed_credential as late as possible before calling wget to avoid logging feed_credential
1184     local remote_path_with_credential="${remote_path}${feed_credential}"
1185     local wget_options="--tries 20 "
1186
1187     local wget_options_extra=''
1188     local wget_result=''
1189
1190     # Test for options that aren't supported on all wget implementations.
1191     if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
1192         wget_options_extra="--waitretry 2 --connect-timeout 15 "
1193     else
1194         say "wget extra options are unavailable for this environment"
1195     fi
1196
1197     if [ -z "$out_path" ]; then
1198         wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1
1199         wget_result=$?
1200     else
1201         wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1
1202         wget_result=$?
1203     fi
1204
1205     if [[ $wget_result != 0 ]]; then
1206         local disable_feed_credential=false
1207         local response=$(get_http_header_wget $remote_path $disable_feed_credential)
1208         http_code=$( echo "$response" | awk '/^  HTTP/{print $2}' | tail -1 )
1209         download_error_msg="Unable to download $remote_path."
1210         if  [[ ! -z $http_code && $http_code != 2* ]]; then
1211             download_error_msg+=" Returned HTTP status code: $http_code."
1212         # wget exit code 4 stands for network-issue
1213         elif [[ $wget_result == 4 ]]; then
1214             download_error_msg+=" Failed to reach the server: connection timeout."
1215         fi
1216         say_verbose "$download_error_msg"
1217         return 1
1218     fi
1219
1220     return 0
1221 }
1222
1223 get_download_link_from_aka_ms() {
1224     eval $invocation
1225
1226     #quality is not supported for LTS or STS channel
1227     #STS maps to current
1228     if [[ ! -z "$normalized_quality"  && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then
1229         normalized_quality=""
1230         say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored."
1231     fi
1232
1233     say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." 
1234
1235     #construct aka.ms link
1236     aka_ms_link="https://aka.ms/dotnet"
1237     if  [ "$internal" = true ]; then
1238         aka_ms_link="$aka_ms_link/internal"
1239     fi
1240     aka_ms_link="$aka_ms_link/$normalized_channel"
1241     if [[ ! -z "$normalized_quality" ]]; then
1242         aka_ms_link="$aka_ms_link/$normalized_quality"
1243     fi
1244     aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz"
1245     say_verbose "Constructed aka.ms link: '$aka_ms_link'."
1246
1247     #get HTTP response
1248     #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function
1249     #otherwise the redirect link would have credentials as well
1250     #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link
1251     disable_feed_credential=true
1252     response="$(get_http_header $aka_ms_link $disable_feed_credential)"
1253
1254     say_verbose "Received response: $response"
1255     # Get results of all the redirects.
1256     http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' )
1257     # They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404).
1258     broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' )
1259
1260     # All HTTP codes are 301 (Moved Permanently), the redirect link exists.
1261     if [[ -z "$broken_redirects" ]]; then
1262         aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r')
1263
1264         if [[ -z "$aka_ms_download_link" ]]; then
1265             say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location."
1266             return 1
1267         fi
1268
1269         say_verbose "The redirect location retrieved: '$aka_ms_download_link'."
1270         return 0
1271     else
1272         say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)."
1273         return 1
1274     fi
1275 }
1276
1277 get_feeds_to_use()
1278 {
1279     feeds=(
1280     "https://dotnetcli.azureedge.net/dotnet"
1281     "https://dotnetbuilds.azureedge.net/public"
1282     )
1283
1284     if [[ -n "$azure_feed" ]]; then
1285         feeds=("$azure_feed")
1286     fi
1287
1288     if [[ "$no_cdn" == "true" ]]; then
1289         feeds=(
1290         "https://dotnetcli.blob.core.windows.net/dotnet"
1291         "https://dotnetbuilds.blob.core.windows.net/public"
1292         )
1293
1294         if [[ -n "$uncached_feed" ]]; then
1295             feeds=("$uncached_feed")
1296         fi
1297     fi
1298 }
1299
1300 # THIS FUNCTION MAY EXIT (if the determined version is already installed).
1301 generate_download_links() {
1302
1303     download_links=()
1304     specific_versions=()
1305     effective_versions=()
1306     link_types=()
1307
1308     # If generate_akams_links returns false, no fallback to old links. Just terminate.
1309     # This function may also 'exit' (if the determined version is already installed).
1310     generate_akams_links || return
1311
1312     # Check other feeds only if we haven't been able to find an aka.ms link.
1313     if [[ "${#download_links[@]}" -lt 1 ]]; then
1314         for feed in ${feeds[@]}
1315         do
1316             # generate_regular_links may also 'exit' (if the determined version is already installed).
1317             generate_regular_links $feed || return
1318         done
1319     fi
1320
1321     if [[ "${#download_links[@]}" -eq 0 ]]; then
1322         say_err "Failed to resolve the exact version number."
1323         return 1
1324     fi
1325
1326     say_verbose "Generated ${#download_links[@]} links."
1327     for link_index in ${!download_links[@]}
1328     do
1329         say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}"
1330     done
1331 }
1332
1333 # THIS FUNCTION MAY EXIT (if the determined version is already installed).
1334 generate_akams_links() {
1335     local valid_aka_ms_link=true;
1336
1337     normalized_version="$(to_lowercase "$version")"
1338     if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then
1339         say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details."
1340         return 1
1341     fi
1342
1343     if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then
1344         # aka.ms links are not needed when exact version is specified via command or json file
1345         return
1346     fi
1347
1348     get_download_link_from_aka_ms || valid_aka_ms_link=false
1349
1350     if [[ "$valid_aka_ms_link" == true ]]; then
1351         say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'."
1352         say_verbose "Downloading using legacy url will not be attempted."
1353
1354         download_link=$aka_ms_download_link
1355
1356         #get version from the path
1357         IFS='/'
1358         read -ra pathElems <<< "$download_link"
1359         count=${#pathElems[@]}
1360         specific_version="${pathElems[count-2]}"
1361         unset IFS;
1362         say_verbose "Version: '$specific_version'."
1363
1364         #Retrieve effective version
1365         effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")"
1366
1367         # Add link info to arrays
1368         download_links+=($download_link)
1369         specific_versions+=($specific_version)
1370         effective_versions+=($effective_version)
1371         link_types+=("aka.ms")
1372
1373         #  Check if the SDK version is already installed.
1374         if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
1375             say "$asset_name with version '$effective_version' is already installed."
1376             exit 0
1377         fi
1378
1379         return 0
1380     fi
1381
1382     # if quality is specified - exit with error - there is no fallback approach
1383     if [ ! -z "$normalized_quality" ]; then
1384         say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'."
1385         say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support."
1386         return 1
1387     fi
1388     say_verbose "Falling back to latest.version file approach."
1389 }
1390
1391 # THIS FUNCTION MAY EXIT (if the determined version is already installed)
1392 # args:
1393 # feed - $1
1394 generate_regular_links() {
1395     local feed="$1"
1396     local valid_legacy_download_link=true
1397
1398     specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0'
1399
1400     if [[ "$specific_version" == '0' ]]; then
1401         say_verbose "Failed to resolve the specific version number using feed '$feed'"
1402         return
1403     fi
1404
1405     effective_version="$(get_specific_product_version "$feed" "$specific_version")"
1406     say_verbose "specific_version=$specific_version"
1407
1408     download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")"
1409     say_verbose "Constructed primary named payload URL: $download_link"
1410
1411     # Add link info to arrays
1412     download_links+=($download_link)
1413     specific_versions+=($specific_version)
1414     effective_versions+=($effective_version)
1415     link_types+=("primary")
1416
1417     legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false
1418
1419     if [ "$valid_legacy_download_link" = true ]; then
1420         say_verbose "Constructed legacy named payload URL: $legacy_download_link"
1421     
1422         download_links+=($legacy_download_link)
1423         specific_versions+=($specific_version)
1424         effective_versions+=($effective_version)
1425         link_types+=("legacy")
1426     else
1427         legacy_download_link=""
1428         say_verbose "Cound not construct a legacy_download_link; omitting..."
1429     fi
1430
1431     #  Check if the SDK version is already installed.
1432     if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
1433         say "$asset_name with version '$effective_version' is already installed."
1434         exit 0
1435     fi
1436 }
1437
1438 print_dry_run() {
1439
1440     say "Payload URLs:"
1441
1442     for link_index in "${!download_links[@]}"
1443         do
1444             say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}"
1445     done
1446
1447     resolved_version=${specific_versions[0]}
1448     repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\"""
1449     
1450     if [ ! -z "$normalized_quality" ]; then
1451         repeatable_command+=" --quality "\""$normalized_quality"\"""
1452     fi
1453
1454     if [[ "$runtime" == "dotnet" ]]; then
1455         repeatable_command+=" --runtime "\""dotnet"\"""
1456     elif [[ "$runtime" == "aspnetcore" ]]; then
1457         repeatable_command+=" --runtime "\""aspnetcore"\"""
1458     fi
1459
1460     repeatable_command+="$non_dynamic_parameters"
1461
1462     if [ -n "$feed_credential" ]; then
1463         repeatable_command+=" --feed-credential "\""<feed_credential>"\"""
1464     fi
1465
1466     say "Repeatable invocation: $repeatable_command"
1467 }
1468
1469 calculate_vars() {
1470     eval $invocation
1471
1472     script_name=$(basename "$0")
1473     normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")"
1474     say_verbose "Normalized architecture: '$normalized_architecture'."
1475     normalized_os="$(get_normalized_os "$user_defined_os")"
1476     say_verbose "Normalized OS: '$normalized_os'."
1477     normalized_quality="$(get_normalized_quality "$quality")"
1478     say_verbose "Normalized quality: '$normalized_quality'."
1479     normalized_channel="$(get_normalized_channel "$channel")"
1480     say_verbose "Normalized channel: '$normalized_channel'."
1481     normalized_product="$(get_normalized_product "$runtime")"
1482     say_verbose "Normalized product: '$normalized_product'."
1483     install_root="$(resolve_installation_path "$install_dir")"
1484     say_verbose "InstallRoot: '$install_root'."
1485
1486     normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")"
1487
1488     if [[ "$runtime" == "dotnet" ]]; then
1489         asset_relative_path="shared/Microsoft.NETCore.App"
1490         asset_name=".NET Core Runtime"
1491     elif [[ "$runtime" == "aspnetcore" ]]; then
1492         asset_relative_path="shared/Microsoft.AspNetCore.App"
1493         asset_name="ASP.NET Core Runtime"
1494     elif [ -z "$runtime" ]; then
1495         asset_relative_path="sdk"
1496         asset_name=".NET Core SDK"
1497     fi
1498
1499     get_feeds_to_use
1500 }
1501
1502 install_dotnet() {
1503     eval $invocation
1504     local download_failed=false
1505     local download_completed=false
1506     local remote_file_size=0
1507
1508     mkdir -p "$install_root"
1509     zip_path="${zip_path:-$(mktemp "$temporary_file_template")}"
1510     say_verbose "Zip path: $zip_path"
1511
1512     for link_index in "${!download_links[@]}"
1513     do
1514         download_link="${download_links[$link_index]}"
1515         specific_version="${specific_versions[$link_index]}"
1516         effective_version="${effective_versions[$link_index]}"
1517         link_type="${link_types[$link_index]}"
1518
1519         say "Attempting to download using $link_type link $download_link"
1520
1521         # The download function will set variables $http_code and $download_error_msg in case of failure.
1522         download_failed=false
1523         download "$download_link" "$zip_path" 2>&1 || download_failed=true
1524
1525         if [ "$download_failed" = true ]; then
1526             case $http_code in
1527             404)
1528                 say "The resource at $link_type link '$download_link' is not available."
1529                 ;;
1530             *)
1531                 say "Failed to download $link_type link '$download_link': $download_error_msg"
1532                 ;;
1533             esac
1534             rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed"
1535         else
1536             download_completed=true
1537             break
1538         fi
1539     done
1540
1541     if [[ "$download_completed" == false ]]; then
1542         say_err "Could not find \`$asset_name\` with version = $specific_version"
1543         say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
1544         return 1
1545     fi
1546
1547     remote_file_size="$(get_remote_file_size "$download_link")"
1548
1549     say "Extracting zip from $download_link"
1550     extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1
1551
1552     #  Check if the SDK version is installed; if not, fail the installation.
1553     # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
1554     if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then
1555         IFS='-'
1556         read -ra verArr <<< "$specific_version"
1557         release_version="${verArr[0]}"
1558         unset IFS;
1559         say_verbose "Checking installation: version = $release_version"
1560         if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then
1561             say "Installed version is $effective_version"
1562             return 0
1563         fi
1564     fi
1565
1566     #  Check if the standard SDK version is installed.
1567     say_verbose "Checking installation: version = $effective_version"
1568     if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
1569         say "Installed version is $effective_version"
1570         return 0
1571     fi
1572
1573     # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
1574     say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues."
1575     say_err "\`$asset_name\` with version = $effective_version failed to install with an error."
1576     return 1
1577 }
1578
1579 args=("$@")
1580
1581 local_version_file_relative_path="/.version"
1582 bin_folder_relative_path=""
1583 temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX"
1584
1585 channel="LTS"
1586 version="Latest"
1587 json_file=""
1588 install_dir="<auto>"
1589 architecture="<auto>"
1590 dry_run=false
1591 no_path=false
1592 no_cdn=false
1593 azure_feed=""
1594 uncached_feed=""
1595 feed_credential=""
1596 verbose=false
1597 runtime=""
1598 runtime_id=""
1599 quality=""
1600 internal=false
1601 override_non_versioned_files=true
1602 non_dynamic_parameters=""
1603 user_defined_os=""
1604
1605 while [ $# -ne 0 ]
1606 do
1607     name="$1"
1608     case "$name" in
1609         -c|--channel|-[Cc]hannel)
1610             shift
1611             channel="$1"
1612             ;;
1613         -v|--version|-[Vv]ersion)
1614             shift
1615             version="$1"
1616             ;;
1617         -q|--quality|-[Qq]uality)
1618             shift
1619             quality="$1"
1620             ;;
1621         --internal|-[Ii]nternal)
1622             internal=true
1623             non_dynamic_parameters+=" $name"
1624             ;;
1625         -i|--install-dir|-[Ii]nstall[Dd]ir)
1626             shift
1627             install_dir="$1"
1628             ;;
1629         --arch|--architecture|-[Aa]rch|-[Aa]rchitecture)
1630             shift
1631             architecture="$1"
1632             ;;
1633         --os|-[Oo][SS])
1634             shift
1635             user_defined_os="$1"
1636             ;;
1637         --shared-runtime|-[Ss]hared[Rr]untime)
1638             say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'."
1639             if [ -z "$runtime" ]; then
1640                 runtime="dotnet"
1641             fi
1642             ;;
1643         --runtime|-[Rr]untime)
1644             shift
1645             runtime="$1"
1646             if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then
1647                 say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'."
1648                 if [[ "$runtime" == "windowsdesktop" ]]; then
1649                     say_err "WindowsDesktop archives are manufactured for Windows platforms only."
1650                 fi
1651                 exit 1
1652             fi
1653             ;;
1654         --dry-run|-[Dd]ry[Rr]un)
1655             dry_run=true
1656             ;;
1657         --no-path|-[Nn]o[Pp]ath)
1658             no_path=true
1659             non_dynamic_parameters+=" $name"
1660             ;;
1661         --verbose|-[Vv]erbose)
1662             verbose=true
1663             non_dynamic_parameters+=" $name"
1664             ;;
1665         --no-cdn|-[Nn]o[Cc]dn)
1666             no_cdn=true
1667             non_dynamic_parameters+=" $name"
1668             ;;
1669         --azure-feed|-[Aa]zure[Ff]eed)
1670             shift
1671             azure_feed="$1"
1672             non_dynamic_parameters+=" $name "\""$1"\"""
1673             ;;
1674         --uncached-feed|-[Uu]ncached[Ff]eed)
1675             shift
1676             uncached_feed="$1"
1677             non_dynamic_parameters+=" $name "\""$1"\"""
1678             ;;
1679         --feed-credential|-[Ff]eed[Cc]redential)
1680             shift
1681             feed_credential="$1"
1682             #feed_credential should start with "?", for it to be added to the end of the link.
1683             #adding "?" at the beginning of the feed_credential if needed.
1684             [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential"
1685             ;;
1686         --runtime-id|-[Rr]untime[Ii]d)
1687             shift
1688             runtime_id="$1"
1689             non_dynamic_parameters+=" $name "\""$1"\"""
1690             say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead."
1691             ;;
1692         --jsonfile|-[Jj][Ss]on[Ff]ile)
1693             shift
1694             json_file="$1"
1695             ;;
1696         --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles)
1697             override_non_versioned_files=false
1698             non_dynamic_parameters+=" $name"
1699             ;;
1700         --keep-zip|-[Kk]eep[Zz]ip)
1701             keep_zip=true
1702             non_dynamic_parameters+=" $name"
1703             ;;
1704         --zip-path|-[Zz]ip[Pp]ath)
1705             shift
1706             zip_path="$1"
1707             ;;
1708         -?|--?|-h|--help|-[Hh]elp)
1709             script_name="$(basename "$0")"
1710             echo ".NET Tools Installer"
1711             echo "Usage: $script_name [-c|--channel <CHANNEL>] [-v|--version <VERSION>] [-p|--prefix <DESTINATION>]"
1712             echo "       $script_name -h|-?|--help"
1713             echo ""
1714             echo "$script_name is a simple command line interface for obtaining dotnet cli."
1715             echo "    Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
1716             echo "    - The SDK needs to be installed without user interaction and without admin rights."
1717             echo "    - The SDK installation doesn't need to persist across multiple CI runs."
1718             echo "    To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer."
1719             echo ""
1720             echo "Options:"
1721             echo "  -c,--channel <CHANNEL>         Download from the channel specified, Defaults to \`$channel\`."
1722             echo "      -Channel"
1723             echo "          Possible values:"
1724             echo "          - STS - the most recent Standard Term Support release"
1725             echo "          - LTS - the most recent Long Term Support release"
1726             echo "          - 2-part version in a format A.B - represents a specific release"
1727             echo "              examples: 2.0; 1.0"
1728             echo "          - 3-part version in a format A.B.Cxx - represents a specific SDK release"
1729             echo "              examples: 5.0.1xx, 5.0.2xx."
1730             echo "              Supported since 5.0 release"
1731             echo "          Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead."
1732             echo "          Note: The version parameter overrides the channel parameter when any version other than 'latest' is used."
1733             echo "  -v,--version <VERSION>         Use specific VERSION, Defaults to \`$version\`."
1734             echo "      -Version"
1735             echo "          Possible values:"
1736             echo "          - latest - the latest build on specific channel"
1737             echo "          - 3-part version in a format A.B.C - represents specific version of build"
1738             echo "              examples: 2.0.0-preview2-006120; 1.1.0"
1739             echo "  -q,--quality <quality>         Download the latest build of specified quality in the channel."
1740             echo "      -Quality"
1741             echo "          The possible values are: daily, signed, validated, preview, GA."
1742             echo "          Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." 
1743             echo "          For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported." 
1744             echo "          Supported since 5.0 release." 
1745             echo "          Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality."
1746             echo "  --internal,-Internal               Download internal builds. Requires providing credentials via --feed-credential parameter."
1747             echo "  --feed-credential <FEEDCREDENTIAL> Token to access Azure feed. Used as a query string to append to the Azure feed."
1748             echo "      -FeedCredential                This parameter typically is not specified."
1749             echo "  -i,--install-dir <DIR>             Install under specified location (see Install Location below)"
1750             echo "      -InstallDir"
1751             echo "  --architecture <ARCHITECTURE>      Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
1752             echo "      --arch,-Architecture,-Arch"
1753             echo "          Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64"
1754             echo "  --os <system>                    Specifies operating system to be used when selecting the installer."
1755             echo "          Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6."
1756             echo "          In case any other value is provided, the platform will be determined by the script based on machine configuration."
1757             echo "          Not supported for legacy links. Use --runtime-id to specify platform for legacy links."
1758             echo "          Refer to: https://aka.ms/dotnet-os-lifecycle for more information."
1759             echo "  --runtime <RUNTIME>                Installs a shared runtime only, without the SDK."
1760             echo "      -Runtime"
1761             echo "          Possible values:"
1762             echo "          - dotnet     - the Microsoft.NETCore.App shared runtime"
1763             echo "          - aspnetcore - the Microsoft.AspNetCore.App shared runtime"
1764             echo "  --dry-run,-DryRun                  Do not perform installation. Display download link."
1765             echo "  --no-path, -NoPath                 Do not set PATH for the current process."
1766             echo "  --verbose,-Verbose                 Display diagnostics information."
1767             echo "  --azure-feed,-AzureFeed            For internal use only."
1768             echo "                                     Allows using a different storage to download SDK archives from."
1769             echo "                                     This parameter is only used if --no-cdn is false."
1770             echo "  --uncached-feed,-UncachedFeed      For internal use only."
1771             echo "                                     Allows using a different storage to download SDK archives from."
1772             echo "                                     This parameter is only used if --no-cdn is true."
1773             echo "  --skip-non-versioned-files         Skips non-versioned files if they already exist, such as the dotnet executable."
1774             echo "      -SkipNonVersionedFiles"
1775             echo "  --no-cdn,-NoCdn                    Disable downloading from the Azure CDN, and use the uncached feed directly."
1776             echo "  --jsonfile <JSONFILE>              Determines the SDK version from a user specified global.json file."
1777             echo "                                     Note: global.json must have a value for 'SDK:Version'"
1778             echo "  --keep-zip,-KeepZip                If set, downloaded file is kept."
1779             echo "  --zip-path, -ZipPath               If set, downloaded file is stored at the specified path."
1780             echo "  -?,--?,-h,--help,-Help             Shows this help message"
1781             echo ""
1782             echo "Install Location:"
1783             echo "  Location is chosen in following order:"
1784             echo "    - --install-dir option"
1785             echo "    - Environmental variable DOTNET_INSTALL_DIR"
1786             echo "    - $HOME/.dotnet"
1787             exit 0
1788             ;;
1789         *)
1790             say_err "Unknown argument \`$name\`"
1791             exit 1
1792             ;;
1793     esac
1794
1795     shift
1796 done
1797
1798 say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
1799 say_verbose "- The SDK needs to be installed without user interaction and without admin rights."
1800 say_verbose "- The SDK installation doesn't need to persist across multiple CI runs."
1801 say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n"
1802
1803 if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then
1804     message="Provide credentials via --feed-credential parameter."
1805     if [ "$dry_run" = true ]; then
1806         say_warning "$message"
1807     else
1808         say_err "$message"
1809         exit 1
1810     fi
1811 fi
1812
1813 check_min_reqs
1814 calculate_vars
1815 # generate_regular_links call below will 'exit' if the determined version is already installed.
1816 generate_download_links
1817
1818 if [[ "$dry_run" = true ]]; then
1819     print_dry_run
1820     exit 0
1821 fi
1822
1823 install_dotnet
1824
1825 bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")"
1826 if [ "$no_path" = false ]; then
1827     say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script."
1828     export PATH="$bin_path":"$PATH"
1829 else
1830     say "Binaries of dotnet can be found in $bin_path"
1831 fi
1832
1833 say "Note that the script does not resolve dependencies during installation."
1834 say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section."
1835 say "Installation finished successfully."