blob: c6c9b8e56cd4c310b6b4da061e3552a827ce0196 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001#!/usr/bin/env perl
2#
3# Copyright (C) 2006 OpenWrt.org
4# Copyright (C) 2016 LEDE project
5#
6# This is free software, licensed under the GNU General Public License v2.
7# See /LICENSE for more information.
8#
9
10use strict;
11use warnings;
12use File::Basename;
13use File::Copy;
14use File::Path;
15use Text::ParseWords;
16use JSON::PP;
17
18@ARGV > 2 or die "Syntax: $0 <target dir> <filename> <hash> <url filename> [<mirror> ...]\n";
19
20my $url_filename;
21my $target = glob(shift @ARGV);
22my $filename = shift @ARGV;
23my $file_hash = shift @ARGV;
24$url_filename = shift @ARGV unless $ARGV[0] =~ /:\/\//;
25my $scriptdir = dirname($0);
26my @mirrors;
27my $ok;
28
29my $check_certificate = $ENV{DOWNLOAD_CHECK_CERTIFICATE} eq "y";
30my $custom_tool = $ENV{DOWNLOAD_TOOL_CUSTOM};
31my $download_tool;
32
33$url_filename or $url_filename = $filename;
34
35sub localmirrors {
36 my @mlist;
37 open LM, "$scriptdir/localmirrors" and do {
38 while (<LM>) {
39 chomp $_;
40 push @mlist, $_ if $_;
41 }
42 close LM;
43 };
44 open CONFIG, "<".$ENV{'TOPDIR'}."/.config" and do {
45 while (<CONFIG>) {
46 /^CONFIG_LOCALMIRROR="(.+)"/ and do {
47 chomp;
48 my @local_mirrors = split(/;/, $1);
49 push @mlist, @local_mirrors;
50 };
51 }
52 close CONFIG;
53 };
54
55 my $mirror = $ENV{'DOWNLOAD_MIRROR'};
56 $mirror and push @mlist, split(/;/, $mirror);
57
58 return @mlist;
59}
60
61sub projectsmirrors {
62 my $project = shift;
63 my $append = shift;
64
65 open (PM, "$scriptdir/projectsmirrors.json") ||
66 die "Can“t open $scriptdir/projectsmirrors.json: $!\n";
67 local $/;
68 my $mirror_json = <PM>;
69 my $mirror = decode_json $mirror_json;
70
71 foreach (@{$mirror->{$project}}) {
72 push @mirrors, $_ . "/" . ($append or "");
73 }
74}
75
76sub which($) {
77 my $prog = shift;
78 my $res = `command -v $prog`;
79 $res or return undef;
80 return $res;
81}
82
83sub hash_cmd() {
84 my $len = length($file_hash);
85 my $cmd;
86
87 $len == 64 and return "$ENV{'MKHASH'} sha256";
88 $len == 32 and return "$ENV{'MKHASH'} md5";
89 return undef;
90}
91
92sub tool_present {
93 my $tool_name = shift;
94 my $compare_line = shift;
95 my $present = 0;
96
97 if (open TOOL, "$tool_name --version 2>/dev/null |") {
98 if (defined(my $line = readline TOOL)) {
99 $present = 1 if $line =~ /^$compare_line /;
100 }
101 close TOOL;
102 }
103
104 return $present
105}
106
107sub select_tool {
108 $custom_tool =~ tr/"//d;
109 if ($custom_tool) {
110 return $custom_tool;
111 }
112
113 # Try to use curl if available
114 if (tool_present("curl", "curl")) {
115 return "curl";
116 }
117
118 # No tool found, fallback to wget
119 return "wget";
120}
121
122sub download_cmd {
123 my $url = shift;
124 my $filename = shift;
125
126 if ($download_tool eq "curl") {
127 return (qw(curl -f --connect-timeout 20 --retry 5 --location),
128 $check_certificate ? () : '--insecure',
129 shellwords($ENV{CURL_OPTIONS} || ''),
130 $url);
131 } elsif ($download_tool eq "wget") {
132 return (qw(wget --tries=5 --timeout=20 --output-document=-),
133 $check_certificate ? () : '--no-check-certificate',
134 shellwords($ENV{WGET_OPTIONS} || ''),
135 $url);
136 } elsif ($download_tool eq "aria2c") {
137 my $additional_mirrors = join(" ", map "$_/$filename", @_);
138 my @chArray = ('a'..'z', 'A'..'Z', 0..9);
139 my $rfn = join '', "${filename}_", map{ $chArray[int rand @chArray] } 0..9;
140
141 @mirrors=();
142
143 return join(" ", "[ -d $ENV{'TMPDIR'}/aria2c ] || mkdir $ENV{'TMPDIR'}/aria2c;",
144 "touch $ENV{'TMPDIR'}/aria2c/${rfn}_spp;",
145 qw(aria2c --stderr -c -x2 -s10 -j10 -k1M), $url, $additional_mirrors,
146 $check_certificate ? () : '--check-certificate=false',
147 "--server-stat-of=$ENV{'TMPDIR'}/aria2c/${rfn}_spp",
148 "--server-stat-if=$ENV{'TMPDIR'}/aria2c/${rfn}_spp",
149 "--daemon=false --no-conf", shellwords($ENV{ARIA2C_OPTIONS} || ''),
150 "-d $ENV{'TMPDIR'}/aria2c -o $rfn;",
151 "cat $ENV{'TMPDIR'}/aria2c/$rfn;",
152 "rm $ENV{'TMPDIR'}/aria2c/$rfn $ENV{'TMPDIR'}/aria2c/${rfn}_spp");
153 } else {
154 return join(" ", $download_tool, $url);
155 }
156}
157
158my $hash_cmd = hash_cmd();
159$hash_cmd or ($file_hash eq "skip") or die "Cannot find appropriate hash command, ensure the provided hash is either a MD5 or SHA256 checksum.\n";
160
161sub download
162{
163 my $mirror = shift;
164 my $download_filename = shift;
165 my @additional_mirrors = @_;
166
167 $mirror =~ s!/$!!;
168
169 if ($mirror =~ s!^file://!!) {
170 if (! -d "$mirror") {
171 print STDERR "Wrong local cache directory -$mirror-.\n";
172 cleanup();
173 return;
174 }
175
176 if (! -d "$target") {
177 make_path($target);
178 }
179
180 if (! open TMPDLS, "find $mirror -follow -name $filename 2>/dev/null |") {
181 print("Failed to search for $filename in $mirror\n");
182 return;
183 }
184
185 my $link;
186
187 while (defined(my $line = readline TMPDLS)) {
188 chomp ($link = $line);
189 if ($. > 1) {
190 print("$. or more instances of $filename in $mirror found . Only one instance allowed.\n");
191 return;
192 }
193 }
194
195 close TMPDLS;
196
197 if (! $link) {
198 print("No instances of $filename found in $mirror.\n");
199 return;
200 }
201
202 print("Copying $filename from $link\n");
203 copy($link, "$target/$filename.dl");
204
205 $hash_cmd and do {
206 if (system("cat '$target/$filename.dl' | $hash_cmd > '$target/$filename.hash'")) {
207 print("Failed to generate hash for $filename\n");
208 return;
209 }
210 };
211 } else {
212 my @cmd = download_cmd("$mirror/$download_filename", $download_filename, @additional_mirrors);
213 print STDERR "+ ".join(" ",@cmd)."\n";
214 open(FETCH_FD, '-|', @cmd) or die "Cannot launch aria2c, curl or wget.\n";
215 $hash_cmd and do {
216 open MD5SUM, "| $hash_cmd > '$target/$filename.hash'" or die "Cannot launch $hash_cmd.\n";
217 };
218 open OUTPUT, "> $target/$filename.dl" or die "Cannot create file $target/$filename.dl: $!\n";
219 my $buffer;
220 while (read FETCH_FD, $buffer, 1048576) {
221 $hash_cmd and print MD5SUM $buffer;
222 print OUTPUT $buffer;
223 }
224 $hash_cmd and close MD5SUM;
225 close FETCH_FD;
226 close OUTPUT;
227
228 if ($? >> 8) {
229 print STDERR "Download failed.\n";
230 cleanup();
231 return;
232 }
233 }
234
235 $hash_cmd and do {
236 my $sum = `cat "$target/$filename.hash"`;
237 $sum =~ /^(\w+)\s*/ or die "Could not generate file hash\n";
238 $sum = $1;
239
240 if ($sum ne $file_hash) {
241 print STDERR "Hash of the downloaded file does not match (file: $sum, requested: $file_hash) - deleting download.\n";
242 cleanup();
243 return;
244 }
245 };
246
247 unlink "$target/$filename";
248 move("$target/$filename.dl", "$target/$filename");
249 cleanup();
250}
251
252sub cleanup
253{
254 unlink "$target/$filename.dl";
255 unlink "$target/$filename.hash";
256}
257
258@mirrors = localmirrors();
259
260foreach my $mirror (@ARGV) {
261 if ($mirror =~ /^\@SF\/(.+)$/) {
262 # give sourceforge a few more tries, because it redirects to different mirrors
263 for (1 .. 5) {
264 projectsmirrors '@SF', $1;
265 }
266 } elsif ($mirror =~ /^\@OPENWRT$/) {
267 # use OpenWrt source server directly
268 } elsif ($mirror =~ /^\@DEBIAN\/(.+)$/) {
269 projectsmirrors '@DEBIAN', $1;
270 } elsif ($mirror =~ /^\@APACHE\/(.+)$/) {
271 projectsmirrors '@APACHE', $1;
272 } elsif ($mirror =~ /^\@GITHUB\/(.+)$/) {
273 # give github a few more tries (different mirrors)
274 for (1 .. 5) {
275 projectsmirrors '@GITHUB', $1;
276 }
277 } elsif ($mirror =~ /^\@GNU\/(.+)$/) {
278 projectsmirrors '@GNU', $1;
279 } elsif ($mirror =~ /^\@SAVANNAH\/(.+)$/) {
280 projectsmirrors '@SAVANNAH', $1;
281 } elsif ($mirror =~ /^\@KERNEL\/(.+)$/) {
282 my @extra = ( $1 );
283 if ($filename =~ /linux-\d+\.\d+(?:\.\d+)?-rc/) {
284 push @extra, "$extra[0]/testing";
285 } elsif ($filename =~ /linux-(\d+\.\d+(?:\.\d+)?)/) {
286 push @extra, "$extra[0]/longterm/v$1";
287 }
288 foreach my $dir (@extra) {
289 projectsmirrors '@KERNEL', $dir;
290 }
291 } elsif ($mirror =~ /^\@GNOME\/(.+)$/) {
292 projectsmirrors '@GNOME', $1;
293 } else {
294 push @mirrors, $mirror;
295 }
296}
297
298projectsmirrors '@OPENWRT';
299
300if (-f "$target/$filename") {
301 $hash_cmd and do {
302 if (system("cat '$target/$filename' | $hash_cmd > '$target/$filename.hash'")) {
303 die "Failed to generate hash for $filename\n";
304 }
305
306 my $sum = `cat "$target/$filename.hash"`;
307 $sum =~ /^(\w+)\s*/ or die "Could not generate file hash\n";
308 $sum = $1;
309
310 cleanup();
311 exit 0 if $sum eq $file_hash;
312
313 die "Hash of the local file $filename does not match (file: $sum, requested: $file_hash) - deleting download.\n";
314 unlink "$target/$filename";
315 };
316}
317
318$download_tool = select_tool();
319
320while (!-f "$target/$filename") {
321 my $mirror = shift @mirrors;
322 $mirror or die "No more mirrors to try - giving up.\n";
323
324 download($mirror, $url_filename, @mirrors);
325 if (!-f "$target/$filename" && $url_filename ne $filename) {
326 download($mirror, $filename, @mirrors);
327 }
328}
329
330$SIG{INT} = \&cleanup;