Commit | Line | Data |
f9916dde |
1 | # -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- |
2 | # vim: ts=4 sts=4 sw=4: |
3 | package CPAN::FTP; |
4 | use strict; |
5 | |
6 | use Fcntl qw(:flock); |
2f2071b1 |
7 | use File::Basename qw(dirname); |
8 | use File::Path qw(mkpath); |
f9916dde |
9 | use CPAN::FTP::netrc; |
10 | use vars qw($connect_to_internet_ok $Ua $Thesite $ThesiteURL $Themethod); |
11 | @CPAN::FTP::ISA = qw(CPAN::Debug); |
12 | |
13 | use vars qw( |
14 | $VERSION |
15 | ); |
87e4a48e |
16 | $VERSION = "5.5002"; |
f9916dde |
17 | |
18 | #-> sub CPAN::FTP::ftp_statistics |
19 | # if they want to rewrite, they need to pass in a filehandle |
20 | sub _ftp_statistics { |
21 | my($self,$fh) = @_; |
22 | my $locktype = $fh ? LOCK_EX : LOCK_SH; |
23 | $fh ||= FileHandle->new; |
24 | my $file = File::Spec->catfile($CPAN::Config->{cpan_home},"FTPstats.yml"); |
2f2071b1 |
25 | mkpath dirname $file; |
f9916dde |
26 | open $fh, "+>>$file" or $CPAN::Frontend->mydie("Could not open '$file': $!"); |
27 | my $sleep = 1; |
28 | my $waitstart; |
29 | while (!CPAN::_flock($fh, $locktype|LOCK_NB)) { |
30 | $waitstart ||= localtime(); |
31 | if ($sleep>3) { |
32 | $CPAN::Frontend->mywarn("Waiting for a read lock on '$file' (since $waitstart)\n"); |
33 | } |
34 | $CPAN::Frontend->mysleep($sleep); |
35 | if ($sleep <= 3) { |
36 | $sleep+=0.33; |
37 | } elsif ($sleep <=6) { |
38 | $sleep+=0.11; |
39 | } |
40 | } |
41 | my $stats = eval { CPAN->_yaml_loadfile($file); }; |
42 | if ($@) { |
43 | if (ref $@) { |
44 | if (ref $@ eq "CPAN::Exception::yaml_not_installed") { |
45 | $CPAN::Frontend->myprint("Warning (usually harmless): $@"); |
46 | return; |
47 | } elsif (ref $@ eq "CPAN::Exception::yaml_process_error") { |
48 | $CPAN::Frontend->mydie($@); |
49 | } |
50 | } else { |
51 | $CPAN::Frontend->mydie($@); |
52 | } |
53 | } |
54 | return $stats->[0]; |
55 | } |
56 | |
57 | #-> sub CPAN::FTP::_mytime |
58 | sub _mytime () { |
59 | if (CPAN->has_inst("Time::HiRes")) { |
60 | return Time::HiRes::time(); |
61 | } else { |
62 | return time; |
63 | } |
64 | } |
65 | |
66 | #-> sub CPAN::FTP::_new_stats |
67 | sub _new_stats { |
68 | my($self,$file) = @_; |
69 | my $ret = { |
70 | file => $file, |
71 | attempts => [], |
72 | start => _mytime, |
73 | }; |
74 | $ret; |
75 | } |
76 | |
77 | #-> sub CPAN::FTP::_add_to_statistics |
78 | sub _add_to_statistics { |
79 | my($self,$stats) = @_; |
80 | my $yaml_module = CPAN::_yaml_module(); |
81 | $self->debug("yaml_module[$yaml_module]") if $CPAN::DEBUG; |
82 | if ($CPAN::META->has_inst($yaml_module)) { |
83 | $stats->{thesiteurl} = $ThesiteURL; |
84 | $stats->{end} = CPAN::FTP::_mytime(); |
85 | my $fh = FileHandle->new; |
86 | my $time = time; |
87 | my $sdebug = 0; |
88 | my @debug; |
89 | @debug = $time if $sdebug; |
90 | my $fullstats = $self->_ftp_statistics($fh); |
91 | close $fh; |
92 | $fullstats->{history} ||= []; |
93 | push @debug, scalar @{$fullstats->{history}} if $sdebug; |
94 | push @debug, time if $sdebug; |
95 | push @{$fullstats->{history}}, $stats; |
96 | # YAML.pm 0.62 is unacceptably slow with 999; |
97 | # YAML::Syck 0.82 has no noticable performance problem with 999; |
98 | my $ftpstats_size = $CPAN::Config->{ftpstats_size} || 99; |
99 | my $ftpstats_period = $CPAN::Config->{ftpstats_period} || 14; |
100 | while ( |
101 | @{$fullstats->{history}} > $ftpstats_size |
102 | || $time - $fullstats->{history}[0]{start} > 86400*$ftpstats_period |
103 | ) { |
104 | shift @{$fullstats->{history}} |
105 | } |
106 | push @debug, scalar @{$fullstats->{history}} if $sdebug; |
107 | push @debug, time if $sdebug; |
108 | push @debug, scalar localtime($fullstats->{history}[0]{start}) if $sdebug; |
109 | # need no eval because if this fails, it is serious |
110 | my $sfile = File::Spec->catfile($CPAN::Config->{cpan_home},"FTPstats.yml"); |
111 | CPAN->_yaml_dumpfile("$sfile.$$",$fullstats); |
112 | if ( $sdebug ) { |
113 | local $CPAN::DEBUG = 512; # FTP |
114 | push @debug, time; |
115 | CPAN->debug(sprintf("DEBUG history: before_read[%d]before[%d]at[%d]". |
116 | "after[%d]at[%d]oldest[%s]dumped backat[%d]", |
117 | @debug, |
118 | )); |
119 | } |
120 | # Win32 cannot rename a file to an existing filename |
7b8f75d3 |
121 | unlink($sfile) if ($^O eq 'MSWin32' or $^O eq 'os2'); |
f9916dde |
122 | _copy_stat($sfile, "$sfile.$$") if -e $sfile; |
123 | rename "$sfile.$$", $sfile |
124 | or $CPAN::Frontend->mydie("Could not rename '$sfile.$$' to '$sfile': $!\n"); |
125 | } |
126 | } |
127 | |
128 | # Copy some stat information (owner, group, mode and) from one file to |
129 | # another. |
130 | # This is a utility function which might be moved to a utility repository. |
131 | #-> sub CPAN::FTP::_copy_stat |
132 | sub _copy_stat { |
133 | my($src, $dest) = @_; |
134 | my @stat = stat($src); |
135 | if (!@stat) { |
136 | $CPAN::Frontend->mywarn("Can't stat '$src': $!\n"); |
137 | return; |
138 | } |
139 | |
140 | eval { |
141 | chmod $stat[2], $dest |
142 | or $CPAN::Frontend->mywarn("Can't chmod '$dest' to " . sprintf("0%o", $stat[2]) . ": $!\n"); |
143 | }; |
144 | warn $@ if $@; |
145 | eval { |
146 | chown $stat[4], $stat[5], $dest |
147 | or do { |
148 | my $save_err = $!; # otherwise it's lost in the get... calls |
149 | $CPAN::Frontend->mywarn("Can't chown '$dest' to " . |
150 | (getpwuid($stat[4]))[0] . "/" . |
151 | (getgrgid($stat[5]))[0] . ": $save_err\n" |
152 | ); |
153 | }; |
154 | }; |
155 | warn $@ if $@; |
156 | } |
157 | |
158 | # if file is CHECKSUMS, suggest the place where we got the file to be |
159 | # checked from, maybe only for young files? |
160 | #-> sub CPAN::FTP::_recommend_url_for |
161 | sub _recommend_url_for { |
162 | my($self, $file) = @_; |
163 | my $urllist = $self->_get_urllist; |
164 | if ($file =~ s|/CHECKSUMS(.gz)?$||) { |
165 | my $fullstats = $self->_ftp_statistics(); |
166 | my $history = $fullstats->{history} || []; |
167 | while (my $last = pop @$history) { |
168 | last if $last->{end} - time > 3600; # only young results are interesting |
169 | next unless $last->{file}; # dirname of nothing dies! |
2f2071b1 |
170 | next unless $file eq dirname($last->{file}); |
f9916dde |
171 | return $last->{thesiteurl}; |
172 | } |
173 | } |
174 | if ($CPAN::Config->{randomize_urllist} |
175 | && |
176 | rand(1) < $CPAN::Config->{randomize_urllist} |
177 | ) { |
178 | $urllist->[int rand scalar @$urllist]; |
179 | } else { |
180 | return (); |
181 | } |
182 | } |
183 | |
184 | #-> sub CPAN::FTP::_get_urllist |
185 | sub _get_urllist { |
186 | my($self) = @_; |
187 | $CPAN::Config->{urllist} ||= []; |
188 | unless (ref $CPAN::Config->{urllist} eq 'ARRAY') { |
189 | $CPAN::Frontend->mywarn("Malformed urllist; ignoring. Configuration file corrupt?\n"); |
190 | $CPAN::Config->{urllist} = []; |
191 | } |
192 | my @urllist = grep { defined $_ and length $_ } @{$CPAN::Config->{urllist}}; |
193 | for my $u (@urllist) { |
194 | CPAN->debug("u[$u]") if $CPAN::DEBUG; |
195 | if (UNIVERSAL::can($u,"text")) { |
196 | $u->{TEXT} .= "/" unless substr($u->{TEXT},-1) eq "/"; |
197 | } else { |
198 | $u .= "/" unless substr($u,-1) eq "/"; |
199 | $u = CPAN::URL->new(TEXT => $u, FROM => "USER"); |
200 | } |
201 | } |
202 | \@urllist; |
203 | } |
204 | |
205 | #-> sub CPAN::FTP::ftp_get ; |
206 | sub ftp_get { |
207 | my($class,$host,$dir,$file,$target) = @_; |
208 | $class->debug( |
209 | qq[Going to fetch file [$file] from dir [$dir] |
210 | on host [$host] as local [$target]\n] |
211 | ) if $CPAN::DEBUG; |
212 | my $ftp = Net::FTP->new($host); |
213 | unless ($ftp) { |
214 | $CPAN::Frontend->mywarn(" Could not connect to host '$host' with Net::FTP\n"); |
215 | return; |
216 | } |
217 | return 0 unless defined $ftp; |
218 | $ftp->debug(1) if $CPAN::DEBUG{'FTP'} & $CPAN::DEBUG; |
219 | $class->debug(qq[Going to login("anonymous","$Config::Config{cf_email}")]); |
220 | unless ( $ftp->login("anonymous",$Config::Config{'cf_email'}) ) { |
221 | my $msg = $ftp->message; |
222 | $CPAN::Frontend->mywarn(" Couldn't login on $host: $msg"); |
223 | return; |
224 | } |
225 | unless ( $ftp->cwd($dir) ) { |
226 | my $msg = $ftp->message; |
227 | $CPAN::Frontend->mywarn(" Couldn't cwd $dir: $msg"); |
228 | return; |
229 | } |
230 | $ftp->binary; |
231 | $class->debug(qq[Going to ->get("$file","$target")\n]) if $CPAN::DEBUG; |
232 | unless ( $ftp->get($file,$target) ) { |
233 | my $msg = $ftp->message; |
234 | $CPAN::Frontend->mywarn(" Couldn't fetch $file from $host: $msg"); |
235 | return; |
236 | } |
237 | $ftp->quit; # it's ok if this fails |
238 | return 1; |
239 | } |
240 | |
241 | # If more accuracy is wanted/needed, Chris Leach sent me this patch... |
242 | |
243 | # > *** /install/perl/live/lib/CPAN.pm- Wed Sep 24 13:08:48 1997 |
244 | # > --- /tmp/cp Wed Sep 24 13:26:40 1997 |
245 | # > *************** |
246 | # > *** 1562,1567 **** |
247 | # > --- 1562,1580 ---- |
248 | # > return 1 if substr($url,0,4) eq "file"; |
249 | # > return 1 unless $url =~ m|://([^/]+)|; |
250 | # > my $host = $1; |
251 | # > + my $proxy = $CPAN::Config->{'http_proxy'} || $ENV{'http_proxy'}; |
252 | # > + if ($proxy) { |
253 | # > + $proxy =~ m|://([^/:]+)|; |
254 | # > + $proxy = $1; |
255 | # > + my $noproxy = $CPAN::Config->{'no_proxy'} || $ENV{'no_proxy'}; |
256 | # > + if ($noproxy) { |
257 | # > + if ($host !~ /$noproxy$/) { |
258 | # > + $host = $proxy; |
259 | # > + } |
260 | # > + } else { |
261 | # > + $host = $proxy; |
262 | # > + } |
263 | # > + } |
264 | # > require Net::Ping; |
265 | # > return 1 unless $Net::Ping::VERSION >= 2; |
266 | # > my $p; |
267 | |
268 | |
269 | #-> sub CPAN::FTP::localize ; |
270 | sub localize { |
271 | my($self,$file,$aslocal,$force) = @_; |
272 | $force ||= 0; |
273 | Carp::croak( "Usage: ->localize(cpan_file,as_local_file[,$force])" ) |
274 | unless defined $aslocal; |
2f2071b1 |
275 | if ($CPAN::DEBUG){ |
276 | require Carp; |
277 | my $longmess = Carp::longmess(); |
278 | $self->debug("file[$file] aslocal[$aslocal] force[$force] carplongmess[$longmess]"); |
279 | } |
f9916dde |
280 | if ($^O eq 'MacOS') { |
281 | # Comment by AK on 2000-09-03: Uniq short filenames would be |
282 | # available in CHECKSUMS file |
283 | my($name, $path) = File::Basename::fileparse($aslocal, ''); |
284 | if (length($name) > 31) { |
285 | $name =~ s/( |
286 | \.( |
287 | readme(\.(gz|Z))? | |
288 | (tar\.)?(gz|Z) | |
289 | tgz | |
290 | zip | |
291 | pm\.(gz|Z) |
292 | ) |
293 | )$//x; |
294 | my $suf = $1; |
295 | my $size = 31 - length($suf); |
296 | while (length($name) > $size) { |
297 | chop $name; |
298 | } |
299 | $name .= $suf; |
300 | $aslocal = File::Spec->catfile($path, $name); |
301 | } |
302 | } |
303 | |
304 | if (-f $aslocal && -r _ && !($force & 1)) { |
305 | my $size; |
306 | if ($size = -s $aslocal) { |
307 | $self->debug("aslocal[$aslocal]size[$size]") if $CPAN::DEBUG; |
308 | return $aslocal; |
309 | } else { |
310 | # empty file from a previous unsuccessful attempt to download it |
311 | unlink $aslocal or |
312 | $CPAN::Frontend->mydie("Found a zero-length '$aslocal' that I ". |
313 | "could not remove."); |
314 | } |
315 | } |
316 | my($maybe_restore) = 0; |
317 | if (-f $aslocal) { |
318 | rename $aslocal, "$aslocal.bak$$"; |
319 | $maybe_restore++; |
320 | } |
321 | |
2f2071b1 |
322 | my($aslocal_dir) = dirname($aslocal); |
f9916dde |
323 | # Inheritance is not easier to manage than a few if/else branches |
324 | if ($CPAN::META->has_usable('LWP::UserAgent')) { |
325 | unless ($Ua) { |
326 | CPAN::LWP::UserAgent->config; |
327 | eval {$Ua = CPAN::LWP::UserAgent->new;}; # Why is has_usable still not fit enough? |
328 | if ($@) { |
329 | $CPAN::Frontend->mywarn("CPAN::LWP::UserAgent->new dies with $@\n") |
330 | if $CPAN::DEBUG; |
331 | } else { |
332 | my($var); |
333 | $Ua->proxy('ftp', $var) |
334 | if $var = $CPAN::Config->{ftp_proxy} || $ENV{ftp_proxy}; |
335 | $Ua->proxy('http', $var) |
336 | if $var = $CPAN::Config->{http_proxy} || $ENV{http_proxy}; |
337 | $Ua->no_proxy($var) |
338 | if $var = $CPAN::Config->{no_proxy} || $ENV{no_proxy}; |
339 | } |
340 | } |
341 | } |
342 | for my $prx (qw(ftp_proxy http_proxy no_proxy)) { |
343 | $ENV{$prx} = $CPAN::Config->{$prx} if $CPAN::Config->{$prx}; |
344 | } |
345 | |
346 | # Try the list of urls for each single object. We keep a record |
347 | # where we did get a file from |
348 | my(@reordered,$last); |
349 | my $ccurllist = $self->_get_urllist; |
350 | $last = $#$ccurllist; |
351 | if ($force & 2) { # local cpans probably out of date, don't reorder |
352 | @reordered = (0..$last); |
353 | } else { |
354 | @reordered = |
355 | sort { |
356 | (substr($ccurllist->[$b],0,4) eq "file") |
357 | <=> |
358 | (substr($ccurllist->[$a],0,4) eq "file") |
359 | or |
360 | defined($ThesiteURL) |
361 | and |
362 | ($ccurllist->[$b] eq $ThesiteURL) |
363 | <=> |
364 | ($ccurllist->[$a] eq $ThesiteURL) |
365 | } 0..$last; |
366 | } |
367 | my(@levels); |
368 | $Themethod ||= ""; |
369 | $self->debug("Themethod[$Themethod]reordered[@reordered]") if $CPAN::DEBUG; |
370 | my @all_levels = ( |
371 | ["dleasy", "file"], |
372 | ["dleasy"], |
373 | ["dlhard"], |
374 | ["dlhardest"], |
375 | ["dleasy", "http","defaultsites"], |
376 | ["dlhard", "http","defaultsites"], |
377 | ["dleasy", "ftp", "defaultsites"], |
378 | ["dlhard", "ftp", "defaultsites"], |
379 | ["dlhardest","", "defaultsites"], |
380 | ); |
381 | if ($Themethod) { |
382 | @levels = grep {$_->[0] eq $Themethod} @all_levels; |
383 | push @levels, grep {$_->[0] ne $Themethod} @all_levels; |
384 | } else { |
385 | @levels = @all_levels; |
386 | } |
387 | @levels = qw/dleasy/ if $^O eq 'MacOS'; |
388 | my($levelno); |
389 | local $ENV{FTP_PASSIVE} = |
390 | exists $CPAN::Config->{ftp_passive} ? |
391 | $CPAN::Config->{ftp_passive} : 1; |
392 | my $ret; |
393 | my $stats = $self->_new_stats($file); |
394 | for ($CPAN::Config->{connect_to_internet_ok}) { |
395 | $connect_to_internet_ok = $_ if not defined $connect_to_internet_ok and defined $_; |
396 | } |
397 | LEVEL: for $levelno (0..$#levels) { |
398 | my $level_tuple = $levels[$levelno]; |
399 | my($level,$scheme,$sitetag) = @$level_tuple; |
2f2071b1 |
400 | $self->mymkpath($aslocal_dir) unless $scheme && "file" eq $scheme; |
f9916dde |
401 | my $defaultsites = $sitetag && $sitetag eq "defaultsites"; |
402 | my @urllist; |
403 | if ($defaultsites) { |
404 | unless (defined $connect_to_internet_ok) { |
405 | $CPAN::Frontend->myprint(sprintf qq{ |
406 | I would like to connect to one of the following sites to get '%s': |
407 | |
408 | %s |
409 | }, |
410 | $file, |
411 | join("",map { " ".$_->text."\n" } @CPAN::Defaultsites), |
412 | ); |
413 | my $answer = CPAN::Shell::colorable_makemaker_prompt("Is it OK to try to connect to the Internet?", "yes"); |
414 | if ($answer =~ /^y/i) { |
415 | $connect_to_internet_ok = 1; |
416 | } else { |
417 | $connect_to_internet_ok = 0; |
418 | } |
419 | } |
420 | if ($connect_to_internet_ok) { |
421 | @urllist = @CPAN::Defaultsites; |
422 | } else { |
2f2071b1 |
423 | my $sleep = 2; |
424 | # the tricky thing about dying here is that everybody |
425 | # believes that calls to exists() or all_objects() are |
426 | # safe. |
427 | require CPAN::Exception::blocked_urllist; |
428 | die CPAN::Exception::blocked_urllist->new; |
f9916dde |
429 | } |
430 | } else { |
431 | my @host_seq = $level =~ /dleasy/ ? |
432 | @reordered : 0..$last; # reordered has file and $Thesiteurl first |
433 | @urllist = map { $ccurllist->[$_] } @host_seq; |
434 | } |
435 | $self->debug("synth. urllist[@urllist]") if $CPAN::DEBUG; |
436 | my $aslocal_tempfile = $aslocal . ".tmp" . $$; |
437 | if (my $recommend = $self->_recommend_url_for($file)) { |
438 | @urllist = grep { $_ ne $recommend } @urllist; |
439 | unshift @urllist, $recommend; |
440 | } |
441 | $self->debug("synth. urllist[@urllist]") if $CPAN::DEBUG; |
442 | $ret = $self->hostdlxxx($level,$scheme,\@urllist,$file,$aslocal_tempfile,$stats); |
443 | if ($ret) { |
444 | CPAN->debug("ret[$ret]aslocal[$aslocal]") if $CPAN::DEBUG; |
445 | if ($ret eq $aslocal_tempfile) { |
446 | # if we got it exactly as we asked for, only then we |
447 | # want to rename |
448 | rename $aslocal_tempfile, $aslocal |
449 | or $CPAN::Frontend->mydie("Error while trying to rename ". |
450 | "'$ret' to '$aslocal': $!"); |
451 | $ret = $aslocal; |
452 | } |
453 | $Themethod = $level; |
454 | my $now = time; |
455 | # utime $now, $now, $aslocal; # too bad, if we do that, we |
456 | # might alter a local mirror |
457 | $self->debug("level[$level]") if $CPAN::DEBUG; |
458 | last LEVEL; |
459 | } else { |
460 | unlink $aslocal_tempfile; |
461 | last if $CPAN::Signal; # need to cleanup |
462 | } |
463 | } |
464 | if ($ret) { |
465 | $stats->{filesize} = -s $ret; |
466 | } |
467 | $self->debug("before _add_to_statistics") if $CPAN::DEBUG; |
468 | $self->_add_to_statistics($stats); |
469 | $self->debug("after _add_to_statistics") if $CPAN::DEBUG; |
470 | if ($ret) { |
471 | unlink "$aslocal.bak$$"; |
472 | return $ret; |
473 | } |
474 | unless ($CPAN::Signal) { |
475 | my(@mess); |
476 | local $" = " "; |
477 | if (@{$CPAN::Config->{urllist}}) { |
478 | push @mess, |
479 | qq{Please check, if the URLs I found in your configuration file \(}. |
480 | join(", ", @{$CPAN::Config->{urllist}}). |
481 | qq{\) are valid.}; |
482 | } else { |
483 | push @mess, qq{Your urllist is empty!}; |
484 | } |
485 | push @mess, qq{The urllist can be edited.}, |
486 | qq{E.g. with 'o conf urllist push ftp://myurl/'}; |
487 | $CPAN::Frontend->mywarn(Text::Wrap::wrap("","","@mess"). "\n\n"); |
6b1bef9a |
488 | $CPAN::Frontend->mydie("Could not fetch $file\n"); |
f9916dde |
489 | } |
490 | if ($maybe_restore) { |
491 | rename "$aslocal.bak$$", $aslocal; |
492 | $CPAN::Frontend->myprint("Trying to get away with old file:\n" . |
493 | $self->ls($aslocal)); |
494 | return $aslocal; |
495 | } |
496 | return; |
497 | } |
498 | |
499 | sub mymkpath { |
500 | my($self, $aslocal_dir) = @_; |
2f2071b1 |
501 | mkpath($aslocal_dir); |
f9916dde |
502 | $CPAN::Frontend->mywarn(qq{Warning: You are not allowed to write into }. |
503 | qq{directory "$aslocal_dir". |
504 | I\'ll continue, but if you encounter problems, they may be due |
505 | to insufficient permissions.\n}) unless -w $aslocal_dir; |
506 | } |
507 | |
508 | sub hostdlxxx { |
509 | my $self = shift; |
510 | my $level = shift; |
511 | my $scheme = shift; |
512 | my $h = shift; |
513 | $h = [ grep /^\Q$scheme\E:/, @$h ] if $scheme; |
514 | my $method = "host$level"; |
515 | $self->$method($h, @_); |
516 | } |
517 | |
518 | sub _set_attempt { |
519 | my($self,$stats,$method,$url) = @_; |
520 | push @{$stats->{attempts}}, { |
521 | method => $method, |
522 | start => _mytime, |
523 | url => $url, |
524 | }; |
525 | } |
526 | |
527 | # package CPAN::FTP; |
c1413a7f |
528 | sub hostdleasy { #called from hostdlxxx |
f9916dde |
529 | my($self,$host_seq,$file,$aslocal,$stats) = @_; |
530 | my($ro_url); |
531 | HOSTEASY: for $ro_url (@$host_seq) { |
532 | $self->_set_attempt($stats,"dleasy",$ro_url); |
533 | my $url .= "$ro_url$file"; |
534 | $self->debug("localizing perlish[$url]") if $CPAN::DEBUG; |
535 | if ($url =~ /^file:/) { |
536 | my $l; |
537 | if ($CPAN::META->has_inst('URI::URL')) { |
538 | my $u = URI::URL->new($url); |
7b8f75d3 |
539 | $l = $u->dir; |
f9916dde |
540 | } else { # works only on Unix, is poorly constructed, but |
541 | # hopefully better than nothing. |
542 | # RFC 1738 says fileurl BNF is |
543 | # fileurl = "file://" [ host | "localhost" ] "/" fpath |
544 | # Thanks to "Mark D. Baushke" <mdb@cisco.com> for |
545 | # the code |
546 | ($l = $url) =~ s|^file://[^/]*/|/|; # discard the host part |
547 | $l =~ s|^file:||; # assume they |
548 | # meant |
549 | # file://localhost |
550 | $l =~ s|^/||s |
551 | if ! -f $l && $l =~ m|^/\w:|; # e.g. /P: |
552 | } |
553 | $self->debug("local file[$l]") if $CPAN::DEBUG; |
554 | if ( -f $l && -r _) { |
555 | $ThesiteURL = $ro_url; |
556 | return $l; |
557 | } |
558 | if ($l =~ /(.+)\.gz$/) { |
559 | my $ungz = $1; |
560 | if ( -f $ungz && -r _) { |
561 | $ThesiteURL = $ro_url; |
562 | return $ungz; |
563 | } |
564 | } |
565 | # Maybe mirror has compressed it? |
566 | if (-f "$l.gz") { |
567 | $self->debug("found compressed $l.gz") if $CPAN::DEBUG; |
568 | eval { CPAN::Tarzip->new("$l.gz")->gunzip($aslocal) }; |
569 | if ( -f $aslocal) { |
570 | $ThesiteURL = $ro_url; |
571 | return $aslocal; |
572 | } |
573 | } |
574 | $CPAN::Frontend->mywarn("Could not find '$l'\n"); |
575 | } |
576 | $self->debug("it was not a file URL") if $CPAN::DEBUG; |
577 | if ($CPAN::META->has_usable('LWP')) { |
578 | $CPAN::Frontend->myprint("Fetching with LWP: |
579 | $url |
580 | "); |
581 | unless ($Ua) { |
582 | CPAN::LWP::UserAgent->config; |
583 | eval { $Ua = CPAN::LWP::UserAgent->new; }; |
584 | if ($@) { |
585 | $CPAN::Frontend->mywarn("CPAN::LWP::UserAgent->new dies with $@\n"); |
586 | } |
587 | } |
588 | my $res = $Ua->mirror($url, $aslocal); |
589 | if ($res->is_success) { |
590 | $ThesiteURL = $ro_url; |
591 | my $now = time; |
592 | utime $now, $now, $aslocal; # download time is more |
593 | # important than upload |
594 | # time |
595 | return $aslocal; |
596 | } elsif ($url !~ /\.gz(?!\n)\Z/) { |
597 | my $gzurl = "$url.gz"; |
598 | $CPAN::Frontend->myprint("Fetching with LWP: |
599 | $gzurl |
600 | "); |
601 | $res = $Ua->mirror($gzurl, "$aslocal.gz"); |
602 | if ($res->is_success) { |
603 | if (eval {CPAN::Tarzip->new("$aslocal.gz")->gunzip($aslocal)}) { |
604 | $ThesiteURL = $ro_url; |
605 | return $aslocal; |
606 | } |
607 | } |
608 | } else { |
609 | $CPAN::Frontend->myprint(sprintf( |
610 | "LWP failed with code[%s] message[%s]\n", |
611 | $res->code, |
612 | $res->message, |
613 | )); |
614 | # Alan Burlison informed me that in firewall environments |
615 | # Net::FTP can still succeed where LWP fails. So we do not |
616 | # skip Net::FTP anymore when LWP is available. |
617 | } |
618 | } else { |
619 | $CPAN::Frontend->mywarn(" LWP not available\n"); |
620 | } |
621 | return if $CPAN::Signal; |
622 | if ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) { |
623 | # that's the nice and easy way thanks to Graham |
624 | $self->debug("recognized ftp") if $CPAN::DEBUG; |
625 | my($host,$dir,$getfile) = ($1,$2,$3); |
626 | if ($CPAN::META->has_usable('Net::FTP')) { |
627 | $dir =~ s|/+|/|g; |
628 | $CPAN::Frontend->myprint("Fetching with Net::FTP: |
629 | $url |
630 | "); |
631 | $self->debug("getfile[$getfile]dir[$dir]host[$host]" . |
632 | "aslocal[$aslocal]") if $CPAN::DEBUG; |
633 | if (CPAN::FTP->ftp_get($host,$dir,$getfile,$aslocal)) { |
634 | $ThesiteURL = $ro_url; |
635 | return $aslocal; |
636 | } |
637 | if ($aslocal !~ /\.gz(?!\n)\Z/) { |
638 | my $gz = "$aslocal.gz"; |
639 | $CPAN::Frontend->myprint("Fetching with Net::FTP |
640 | $url.gz |
641 | "); |
642 | if (CPAN::FTP->ftp_get($host, |
643 | $dir, |
644 | "$getfile.gz", |
645 | $gz) && |
646 | eval{CPAN::Tarzip->new($gz)->gunzip($aslocal)} |
647 | ) { |
648 | $ThesiteURL = $ro_url; |
649 | return $aslocal; |
650 | } |
651 | } |
652 | # next HOSTEASY; |
653 | } else { |
654 | CPAN->debug("Net::FTP does not count as usable atm") if $CPAN::DEBUG; |
655 | } |
656 | } |
657 | if ( |
658 | UNIVERSAL::can($ro_url,"text") |
659 | and |
660 | $ro_url->{FROM} eq "USER" |
661 | ) { |
662 | ##address #17973: default URLs should not try to override |
663 | ##user-defined URLs just because LWP is not available |
664 | my $ret = $self->hostdlhard([$ro_url],$file,$aslocal,$stats); |
665 | return $ret if $ret; |
666 | } |
667 | return if $CPAN::Signal; |
668 | } |
669 | } |
670 | |
671 | # package CPAN::FTP; |
672 | sub hostdlhard { |
673 | my($self,$host_seq,$file,$aslocal,$stats) = @_; |
674 | |
675 | # Came back if Net::FTP couldn't establish connection (or |
676 | # failed otherwise) Maybe they are behind a firewall, but they |
677 | # gave us a socksified (or other) ftp program... |
678 | |
679 | my($ro_url); |
680 | my($devnull) = $CPAN::Config->{devnull} || ""; |
681 | # < /dev/null "; |
2f2071b1 |
682 | my($aslocal_dir) = dirname($aslocal); |
683 | mkpath($aslocal_dir); |
6b1bef9a |
684 | my $some_dl_success = 0; |
685 | HOSTHARD: for $ro_url (@$host_seq) { |
f9916dde |
686 | $self->_set_attempt($stats,"dlhard",$ro_url); |
687 | my $url = "$ro_url$file"; |
688 | my($proto,$host,$dir,$getfile); |
689 | |
690 | # Courtesy Mark Conty mark_conty@cargill.com change from |
691 | # if ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) { |
692 | # to |
693 | if ($url =~ m|^([^:]+)://(.*?)/(.*)/(.*)|) { |
694 | # proto not yet used |
695 | ($proto,$host,$dir,$getfile) = ($1,$2,$3,$4); |
696 | } else { |
697 | next HOSTHARD; # who said, we could ftp anything except ftp? |
698 | } |
699 | next HOSTHARD if $proto eq "file"; # file URLs would have had |
700 | # success above. Likely a bogus URL |
701 | |
702 | $self->debug("localizing funkyftpwise[$url]") if $CPAN::DEBUG; |
703 | |
704 | # Try the most capable first and leave ncftp* for last as it only |
705 | # does FTP. |
706 | my $proxy_vars = $self->_proxy_vars($ro_url); |
707 | DLPRG: for my $f (qw(curl wget lynx ncftpget ncftp)) { |
708 | my $funkyftp = CPAN::HandleConfig->safe_quote($CPAN::Config->{$f}); |
6b1bef9a |
709 | next DLPRG unless defined $funkyftp; |
710 | next DLPRG if $funkyftp =~ /^\s*$/; |
f9916dde |
711 | |
712 | my($asl_ungz, $asl_gz); |
713 | ($asl_ungz = $aslocal) =~ s/\.gz//; |
714 | $asl_gz = "$asl_ungz.gz"; |
715 | |
716 | my($src_switch) = ""; |
717 | my($chdir) = ""; |
718 | my($stdout_redir) = " > $asl_ungz"; |
719 | if ($f eq "lynx") { |
720 | $src_switch = " -source"; |
721 | } elsif ($f eq "ncftp") { |
722 | $src_switch = " -c"; |
723 | } elsif ($f eq "wget") { |
724 | $src_switch = " -O $asl_ungz"; |
725 | $stdout_redir = ""; |
726 | } elsif ($f eq 'curl') { |
727 | $src_switch = ' -L -f -s -S --netrc-optional'; |
728 | if ($proxy_vars->{http_proxy}) { |
729 | $src_switch .= qq{ -U "$proxy_vars->{proxy_user}:$proxy_vars->{proxy_pass}" -x "$proxy_vars->{http_proxy}"}; |
730 | } |
731 | } |
732 | |
733 | if ($f eq "ncftpget") { |
734 | $chdir = "cd $aslocal_dir && "; |
735 | $stdout_redir = ""; |
736 | } |
737 | $CPAN::Frontend->myprint( |
738 | qq[ |
739 | Trying with "$funkyftp$src_switch" to get |
740 | "$url" |
741 | ]); |
742 | my($system) = |
743 | "$chdir$funkyftp$src_switch \"$url\" $devnull$stdout_redir"; |
744 | $self->debug("system[$system]") if $CPAN::DEBUG; |
745 | my($wstatus) = system($system); |
746 | if ($f eq "lynx") { |
747 | # lynx returns 0 when it fails somewhere |
748 | if (-s $asl_ungz) { |
749 | my $content = do { local *FH; |
750 | open FH, $asl_ungz or die; |
751 | local $/; |
752 | <FH> }; |
753 | if ($content =~ /^<.*(<title>[45]|Error [45])/si) { |
754 | $CPAN::Frontend->mywarn(qq{ |
755 | No success, the file that lynx has downloaded looks like an error message: |
756 | $content |
757 | }); |
758 | $CPAN::Frontend->mysleep(1); |
759 | next DLPRG; |
760 | } |
6b1bef9a |
761 | $some_dl_success++; |
f9916dde |
762 | } else { |
763 | $CPAN::Frontend->myprint(qq{ |
764 | No success, the file that lynx has downloaded is an empty file. |
765 | }); |
766 | next DLPRG; |
767 | } |
768 | } |
769 | if ($wstatus == 0) { |
770 | if (-s $aslocal) { |
771 | # Looks good |
6b1bef9a |
772 | $some_dl_success++; |
f9916dde |
773 | } elsif ($asl_ungz ne $aslocal) { |
774 | # test gzip integrity |
775 | if (eval{CPAN::Tarzip->new($asl_ungz)->gtest}) { |
776 | # e.g. foo.tar is gzipped --> foo.tar.gz |
777 | rename $asl_ungz, $aslocal; |
6b1bef9a |
778 | $some_dl_success++; |
f9916dde |
779 | } else { |
780 | eval{CPAN::Tarzip->new($asl_gz)->gzip($asl_ungz)}; |
6b1bef9a |
781 | if ($@) { |
782 | warn "Warning: $@"; |
783 | } else { |
784 | $some_dl_success++; |
785 | } |
f9916dde |
786 | } |
787 | } |
788 | $ThesiteURL = $ro_url; |
789 | return $aslocal; |
790 | } elsif ($url !~ /\.gz(?!\n)\Z/) { |
791 | unlink $asl_ungz if |
792 | -f $asl_ungz && -s _ == 0; |
793 | my $gz = "$aslocal.gz"; |
794 | my $gzurl = "$url.gz"; |
795 | $CPAN::Frontend->myprint( |
796 | qq[ |
797 | Trying with "$funkyftp$src_switch" to get |
798 | "$url.gz" |
799 | ]); |
800 | my($system) = "$funkyftp$src_switch \"$url.gz\" $devnull > $asl_gz"; |
801 | $self->debug("system[$system]") if $CPAN::DEBUG; |
802 | my($wstatus); |
803 | if (($wstatus = system($system)) == 0 |
804 | && |
805 | -s $asl_gz |
806 | ) { |
807 | # test gzip integrity |
808 | my $ct = eval{CPAN::Tarzip->new($asl_gz)}; |
809 | if ($ct && $ct->gtest) { |
810 | $ct->gunzip($aslocal); |
811 | } else { |
812 | # somebody uncompressed file for us? |
813 | rename $asl_ungz, $aslocal; |
814 | } |
815 | $ThesiteURL = $ro_url; |
816 | return $aslocal; |
817 | } else { |
818 | unlink $asl_gz if -f $asl_gz; |
819 | } |
820 | } else { |
821 | my $estatus = $wstatus >> 8; |
822 | my $size = -f $aslocal ? |
823 | ", left\n$aslocal with size ".-s _ : |
824 | "\nWarning: expected file [$aslocal] doesn't exist"; |
825 | $CPAN::Frontend->myprint(qq{ |
826 | Function system("$system") |
827 | returned status $estatus (wstat $wstatus)$size |
828 | }); |
829 | } |
830 | return if $CPAN::Signal; |
6b1bef9a |
831 | } # download/transfer programs (DLPRG) |
f9916dde |
832 | } # host |
6b1bef9a |
833 | if ($some_dl_success) { |
87e4a48e |
834 | $CPAN::Frontend->mywarn("Warning: doesn't seem we had substantial success downloading '$aslocal'. Don't know how to proceed."); |
6b1bef9a |
835 | } else { |
87e4a48e |
836 | $CPAN::Frontend->mywarn("Warning: no success downloading '$aslocal'. Giving up on it."); |
6b1bef9a |
837 | } |
838 | $CPAN::Frontend->mysleep(5); |
839 | return; |
f9916dde |
840 | } |
841 | |
842 | #-> CPAN::FTP::_proxy_vars |
843 | sub _proxy_vars { |
844 | my($self,$url) = @_; |
845 | my $ret = +{}; |
846 | my $http_proxy = $CPAN::Config->{'http_proxy'} || $ENV{'http_proxy'}; |
847 | if ($http_proxy) { |
848 | my($host) = $url =~ m|://([^/:]+)|; |
849 | my $want_proxy = 1; |
850 | my $noproxy = $CPAN::Config->{'no_proxy'} || $ENV{'no_proxy'} || ""; |
851 | my @noproxy = split /\s*,\s*/, $noproxy; |
852 | if ($host) { |
853 | DOMAIN: for my $domain (@noproxy) { |
854 | if ($host =~ /\Q$domain\E$/) { # cf. LWP::UserAgent |
855 | $want_proxy = 0; |
856 | last DOMAIN; |
857 | } |
858 | } |
859 | } else { |
860 | $CPAN::Frontend->mywarn(" Could not determine host from http_proxy '$http_proxy'\n"); |
861 | } |
862 | if ($want_proxy) { |
863 | my($user, $pass) = |
864 | &CPAN::LWP::UserAgent::get_proxy_credentials(); |
865 | $ret = { |
866 | proxy_user => $user, |
867 | proxy_pass => $pass, |
868 | http_proxy => $http_proxy |
869 | }; |
870 | } |
871 | } |
872 | return $ret; |
873 | } |
874 | |
875 | # package CPAN::FTP; |
876 | sub hostdlhardest { |
877 | my($self,$host_seq,$file,$aslocal,$stats) = @_; |
878 | |
879 | return unless @$host_seq; |
880 | my($ro_url); |
2f2071b1 |
881 | my($aslocal_dir) = dirname($aslocal); |
882 | mkpath($aslocal_dir); |
f9916dde |
883 | my $ftpbin = $CPAN::Config->{ftp}; |
884 | unless ($ftpbin && length $ftpbin && MM->maybe_command($ftpbin)) { |
885 | $CPAN::Frontend->myprint("No external ftp command available\n\n"); |
886 | return; |
887 | } |
888 | $CPAN::Frontend->mywarn(qq{ |
c1413a7f |
889 | As a last resort we now switch to the external ftp command '$ftpbin' |
f9916dde |
890 | to get '$aslocal'. |
891 | |
892 | Doing so often leads to problems that are hard to diagnose. |
893 | |
894 | If you're the victim of such problems, please consider unsetting the |
895 | ftp config variable with |
896 | |
897 | o conf ftp "" |
898 | o conf commit |
899 | |
900 | }); |
901 | $CPAN::Frontend->mysleep(2); |
902 | HOSTHARDEST: for $ro_url (@$host_seq) { |
903 | $self->_set_attempt($stats,"dlhardest",$ro_url); |
904 | my $url = "$ro_url$file"; |
905 | $self->debug("localizing ftpwise[$url]") if $CPAN::DEBUG; |
906 | unless ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) { |
907 | next; |
908 | } |
909 | my($host,$dir,$getfile) = ($1,$2,$3); |
910 | my $timestamp = 0; |
911 | my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime, |
912 | $ctime,$blksize,$blocks) = stat($aslocal); |
913 | $timestamp = $mtime ||= 0; |
914 | my($netrc) = CPAN::FTP::netrc->new; |
915 | my($netrcfile) = $netrc->netrc; |
916 | my($verbose) = $CPAN::DEBUG{'FTP'} & $CPAN::DEBUG ? " -v" : ""; |
917 | my $targetfile = File::Basename::basename($aslocal); |
918 | my(@dialog); |
919 | push( |
920 | @dialog, |
921 | "lcd $aslocal_dir", |
922 | "cd /", |
923 | map("cd $_", split /\//, $dir), # RFC 1738 |
924 | "bin", |
925 | "get $getfile $targetfile", |
926 | "quit" |
927 | ); |
928 | if (! $netrcfile) { |
929 | CPAN->debug("No ~/.netrc file found") if $CPAN::DEBUG; |
930 | } elsif ($netrc->hasdefault || $netrc->contains($host)) { |
931 | CPAN->debug(sprintf("hasdef[%d]cont($host)[%d]", |
932 | $netrc->hasdefault, |
933 | $netrc->contains($host))) if $CPAN::DEBUG; |
934 | if ($netrc->protected) { |
935 | my $dialog = join "", map { " $_\n" } @dialog; |
936 | my $netrc_explain; |
937 | if ($netrc->contains($host)) { |
938 | $netrc_explain = "Relying that your .netrc entry for '$host' ". |
939 | "manages the login"; |
940 | } else { |
941 | $netrc_explain = "Relying that your default .netrc entry ". |
942 | "manages the login"; |
943 | } |
944 | $CPAN::Frontend->myprint(qq{ |
945 | Trying with external ftp to get |
946 | '$url' |
947 | $netrc_explain |
948 | Going to send the dialog |
949 | $dialog |
950 | } |
951 | ); |
952 | $self->talk_ftp("$ftpbin$verbose $host", |
953 | @dialog); |
954 | ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, |
955 | $atime,$mtime,$ctime,$blksize,$blocks) = stat($aslocal); |
956 | $mtime ||= 0; |
957 | if ($mtime > $timestamp) { |
958 | $CPAN::Frontend->myprint("GOT $aslocal\n"); |
959 | $ThesiteURL = $ro_url; |
960 | return $aslocal; |
961 | } else { |
962 | $CPAN::Frontend->myprint("Hmm... Still failed!\n"); |
963 | } |
964 | return if $CPAN::Signal; |
965 | } else { |
966 | $CPAN::Frontend->mywarn(qq{Your $netrcfile is not }. |
967 | qq{correctly protected.\n}); |
968 | } |
969 | } else { |
970 | $CPAN::Frontend->mywarn("Your ~/.netrc neither contains $host |
971 | nor does it have a default entry\n"); |
972 | } |
973 | |
974 | # OK, they don't have a valid ~/.netrc. Use 'ftp -n' |
975 | # then and login manually to host, using e-mail as |
976 | # password. |
977 | $CPAN::Frontend->myprint(qq{Issuing "$ftpbin$verbose -n"\n}); |
978 | unshift( |
979 | @dialog, |
980 | "open $host", |
981 | "user anonymous $Config::Config{'cf_email'}" |
982 | ); |
983 | my $dialog = join "", map { " $_\n" } @dialog; |
984 | $CPAN::Frontend->myprint(qq{ |
985 | Trying with external ftp to get |
986 | $url |
987 | Going to send the dialog |
988 | $dialog |
989 | } |
990 | ); |
991 | $self->talk_ftp("$ftpbin$verbose -n", @dialog); |
992 | ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, |
993 | $atime,$mtime,$ctime,$blksize,$blocks) = stat($aslocal); |
994 | $mtime ||= 0; |
995 | if ($mtime > $timestamp) { |
996 | $CPAN::Frontend->myprint("GOT $aslocal\n"); |
997 | $ThesiteURL = $ro_url; |
998 | return $aslocal; |
999 | } else { |
1000 | $CPAN::Frontend->myprint("Bad luck... Still failed!\n"); |
1001 | } |
1002 | return if $CPAN::Signal; |
1003 | $CPAN::Frontend->mywarn("Can't access URL $url.\n\n"); |
1004 | $CPAN::Frontend->mysleep(2); |
1005 | } # host |
1006 | } |
1007 | |
1008 | # package CPAN::FTP; |
1009 | sub talk_ftp { |
1010 | my($self,$command,@dialog) = @_; |
1011 | my $fh = FileHandle->new; |
1012 | $fh->open("|$command") or die "Couldn't open ftp: $!"; |
1013 | foreach (@dialog) { $fh->print("$_\n") } |
1014 | $fh->close; # Wait for process to complete |
1015 | my $wstatus = $?; |
1016 | my $estatus = $wstatus >> 8; |
1017 | $CPAN::Frontend->myprint(qq{ |
1018 | Subprocess "|$command" |
1019 | returned status $estatus (wstat $wstatus) |
1020 | }) if $wstatus; |
1021 | } |
1022 | |
1023 | # find2perl needs modularization, too, all the following is stolen |
1024 | # from there |
1025 | # CPAN::FTP::ls |
1026 | sub ls { |
1027 | my($self,$name) = @_; |
1028 | my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$sizemm, |
1029 | $atime,$mtime,$ctime,$blksize,$blocks) = lstat($name); |
1030 | |
1031 | my($perms,%user,%group); |
1032 | my $pname = $name; |
1033 | |
1034 | if ($blocks) { |
1035 | $blocks = int(($blocks + 1) / 2); |
1036 | } |
1037 | else { |
1038 | $blocks = int(($sizemm + 1023) / 1024); |
1039 | } |
1040 | |
1041 | if (-f _) { $perms = '-'; } |
1042 | elsif (-d _) { $perms = 'd'; } |
1043 | elsif (-c _) { $perms = 'c'; $sizemm = &sizemm; } |
1044 | elsif (-b _) { $perms = 'b'; $sizemm = &sizemm; } |
1045 | elsif (-p _) { $perms = 'p'; } |
1046 | elsif (-S _) { $perms = 's'; } |
1047 | else { $perms = 'l'; $pname .= ' -> ' . readlink($_); } |
1048 | |
1049 | my(@rwx) = ('---','--x','-w-','-wx','r--','r-x','rw-','rwx'); |
1050 | my(@moname) = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); |
1051 | my $tmpmode = $mode; |
1052 | my $tmp = $rwx[$tmpmode & 7]; |
1053 | $tmpmode >>= 3; |
1054 | $tmp = $rwx[$tmpmode & 7] . $tmp; |
1055 | $tmpmode >>= 3; |
1056 | $tmp = $rwx[$tmpmode & 7] . $tmp; |
1057 | substr($tmp,2,1) =~ tr/-x/Ss/ if -u _; |
1058 | substr($tmp,5,1) =~ tr/-x/Ss/ if -g _; |
1059 | substr($tmp,8,1) =~ tr/-x/Tt/ if -k _; |
1060 | $perms .= $tmp; |
1061 | |
1062 | my $user = $user{$uid} || $uid; # too lazy to implement lookup |
1063 | my $group = $group{$gid} || $gid; |
1064 | |
1065 | my($sec,$min,$hour,$mday,$mon,$year) = localtime($mtime); |
1066 | my($timeyear); |
1067 | my($moname) = $moname[$mon]; |
1068 | if (-M _ > 365.25 / 2) { |
1069 | $timeyear = $year + 1900; |
1070 | } |
1071 | else { |
1072 | $timeyear = sprintf("%02d:%02d", $hour, $min); |
1073 | } |
1074 | |
1075 | sprintf "%5lu %4ld %-10s %2d %-8s %-8s %8s %s %2d %5s %s\n", |
1076 | $ino, |
1077 | $blocks, |
1078 | $perms, |
1079 | $nlink, |
1080 | $user, |
1081 | $group, |
1082 | $sizemm, |
1083 | $moname, |
1084 | $mday, |
1085 | $timeyear, |
1086 | $pname; |
1087 | } |
1088 | |
1089 | 1; |