1package File::Spec::VMS; 2 3use strict; 4use vars qw(@ISA $VERSION); 5require File::Spec::Unix; 6 7$VERSION = '3.48_03'; 8$VERSION =~ tr/_//; 9 10@ISA = qw(File::Spec::Unix); 11 12use File::Basename; 13use VMS::Filespec; 14 15=head1 NAME 16 17File::Spec::VMS - methods for VMS file specs 18 19=head1 SYNOPSIS 20 21 require File::Spec::VMS; # Done internally by File::Spec if needed 22 23=head1 DESCRIPTION 24 25See File::Spec::Unix for a documentation of the methods provided 26there. This package overrides the implementation of these methods, not 27the semantics. 28 29The default behavior is to allow either VMS or Unix syntax on input and to 30return VMS syntax on output unless Unix syntax has been explicitly requested 31via the C<DECC$FILENAME_UNIX_REPORT> CRTL feature. 32 33=over 4 34 35=cut 36 37# Need to look up the feature settings. The preferred way is to use the 38# VMS::Feature module, but that may not be available to dual life modules. 39 40my $use_feature; 41BEGIN { 42 if (eval { local $SIG{__DIE__}; 43 local @INC = @INC; 44 pop @INC if $INC[-1] eq '.'; 45 require VMS::Feature; }) { 46 $use_feature = 1; 47 } 48} 49 50# Need to look up the UNIX report mode. This may become a dynamic mode 51# in the future. 52sub _unix_rpt { 53 my $unix_rpt; 54 if ($use_feature) { 55 $unix_rpt = VMS::Feature::current("filename_unix_report"); 56 } else { 57 my $env_unix_rpt = $ENV{'DECC$FILENAME_UNIX_REPORT'} || ''; 58 $unix_rpt = $env_unix_rpt =~ /^[ET1]/i; 59 } 60 return $unix_rpt; 61} 62 63=item canonpath (override) 64 65Removes redundant portions of file specifications and returns results 66in native syntax unless Unix filename reporting has been enabled. 67 68=cut 69 70 71sub canonpath { 72 my($self,$path) = @_; 73 74 return undef unless defined $path; 75 76 my $unix_rpt = $self->_unix_rpt; 77 78 if ($path =~ m|/|) { 79 my $pathify = $path =~ m|/\Z(?!\n)|; 80 $path = $self->SUPER::canonpath($path); 81 82 return $path if $unix_rpt; 83 $path = $pathify ? vmspath($path) : vmsify($path); 84 } 85 86 $path =~ s/(?<!\^)</[/; # < and > ==> [ and ] 87 $path =~ s/(?<!\^)>/]/; 88 $path =~ s/(?<!\^)\]\[\./\.\]\[/g; # ][. ==> .][ 89 $path =~ s/(?<!\^)\[000000\.\]\[/\[/g; # [000000.][ ==> [ 90 $path =~ s/(?<!\^)\[000000\./\[/g; # [000000. ==> [ 91 $path =~ s/(?<!\^)\.\]\[000000\]/\]/g; # .][000000] ==> ] 92 $path =~ s/(?<!\^)\.\]\[/\./g; # foo.][bar ==> foo.bar 93 1 while ($path =~ s/(?<!\^)([\[\.])(-+)\.(-+)([\.\]])/$1$2$3$4/); 94 # That loop does the following 95 # with any amount of dashes: 96 # .-.-. ==> .--. 97 # [-.-. ==> [--. 98 # .-.-] ==> .--] 99 # [-.-] ==> [--] 100 1 while ($path =~ s/(?<!\^)([\[\.])[^\]\.]+\.-(-+)([\]\.])/$1$2$3/); 101 # That loop does the following 102 # with any amount (minimum 2) 103 # of dashes: 104 # .foo.--. ==> .-. 105 # .foo.--] ==> .-] 106 # [foo.--. ==> [-. 107 # [foo.--] ==> [-] 108 # 109 # And then, the remaining cases 110 $path =~ s/(?<!\^)\[\.-/[-/; # [.- ==> [- 111 $path =~ s/(?<!\^)\.[^\]\.]+\.-\./\./g; # .foo.-. ==> . 112 $path =~ s/(?<!\^)\[[^\]\.]+\.-\./\[/g; # [foo.-. ==> [ 113 $path =~ s/(?<!\^)\.[^\]\.]+\.-\]/\]/g; # .foo.-] ==> ] 114 # [foo.-] ==> [000000] 115 $path =~ s/(?<!\^)\[[^\]\.]+\.-\]/\[000000\]/g; 116 # [] ==> 117 $path =~ s/(?<!\^)\[\]// unless $path eq '[]'; 118 return $unix_rpt ? unixify($path) : $path; 119} 120 121=item catdir (override) 122 123Concatenates a list of file specifications, and returns the result as a 124native directory specification unless the Unix filename reporting feature 125has been enabled. No check is made for "impossible" cases (e.g. elements 126other than the first being absolute filespecs). 127 128=cut 129 130sub catdir { 131 my $self = shift; 132 my $dir = pop; 133 134 my $unix_rpt = $self->_unix_rpt; 135 136 my @dirs = grep {defined() && length()} @_; 137 138 my $rslt; 139 if (@dirs) { 140 my $path = (@dirs == 1 ? $dirs[0] : $self->catdir(@dirs)); 141 my ($spath,$sdir) = ($path,$dir); 142 $spath =~ s/\.dir\Z(?!\n)//i; $sdir =~ s/\.dir\Z(?!\n)//i; 143 144 if ($unix_rpt) { 145 $spath = unixify($spath) unless $spath =~ m#/#; 146 $sdir= unixify($sdir) unless $sdir =~ m#/#; 147 return $self->SUPER::catdir($spath, $sdir) 148 } 149 150 $sdir = $self->eliminate_macros($sdir) unless $sdir =~ /^[\w\-]+\Z(?!\n)/s; 151 $rslt = $self->fixpath($self->eliminate_macros($spath)."/$sdir",1); 152 153 # Special case for VMS absolute directory specs: these will have 154 # had device prepended during trip through Unix syntax in 155 # eliminate_macros(), since Unix syntax has no way to express 156 # "absolute from the top of this device's directory tree". 157 if ($spath =~ /^[\[<][^.\-]/s) { $rslt =~ s/^[^\[<]+//s; } 158 159 } else { 160 # Single directory. Return an empty string on null input; otherwise 161 # just return a canonical path. 162 163 if (not defined $dir or not length $dir) { 164 $rslt = ''; 165 } else { 166 $rslt = $unix_rpt ? $dir : vmspath($dir); 167 } 168 } 169 return $self->canonpath($rslt); 170} 171 172=item catfile (override) 173 174Concatenates a list of directory specifications with a filename specification 175to build a path. 176 177=cut 178 179sub catfile { 180 my $self = shift; 181 my $tfile = pop(); 182 my $file = $self->canonpath($tfile); 183 my @files = grep {defined() && length()} @_; 184 185 my $unix_rpt = $self->_unix_rpt; 186 187 my $rslt; 188 if (@files) { 189 my $path = (@files == 1 ? $files[0] : $self->catdir(@files)); 190 my $spath = $path; 191 192 # Something building a VMS path in pieces may try to pass a 193 # directory name in filename format, so normalize it. 194 $spath =~ s/\.dir\Z(?!\n)//i; 195 196 # If the spath ends with a directory delimiter and the file is bare, 197 # then just concatenate them. 198 if ($spath =~ /^(?<!\^)[^\)\]\/:>]+\)\Z(?!\n)/s && basename($file) eq $file) { 199 $rslt = "$spath$file"; 200 } else { 201 $rslt = $self->eliminate_macros($spath); 202 $rslt .= (defined($rslt) && length($rslt) ? '/' : '') . unixify($file); 203 $rslt = vmsify($rslt) unless $unix_rpt; 204 } 205 } 206 else { 207 # Only passed a single file? 208 my $xfile = (defined($file) && length($file)) ? $file : ''; 209 210 $rslt = $unix_rpt ? $file : vmsify($file); 211 } 212 return $self->canonpath($rslt) unless $unix_rpt; 213 214 # In Unix report mode, do not strip off redundant path information. 215 return $rslt; 216} 217 218 219=item curdir (override) 220 221Returns a string representation of the current directory: '[]' or '.' 222 223=cut 224 225sub curdir { 226 my $self = shift @_; 227 return '.' if ($self->_unix_rpt); 228 return '[]'; 229} 230 231=item devnull (override) 232 233Returns a string representation of the null device: '_NLA0:' or '/dev/null' 234 235=cut 236 237sub devnull { 238 my $self = shift @_; 239 return '/dev/null' if ($self->_unix_rpt); 240 return "_NLA0:"; 241} 242 243=item rootdir (override) 244 245Returns a string representation of the root directory: 'SYS$DISK:[000000]' 246or '/' 247 248=cut 249 250sub rootdir { 251 my $self = shift @_; 252 if ($self->_unix_rpt) { 253 # Root may exist, try it first. 254 my $try = '/'; 255 my ($dev1, $ino1) = stat('/'); 256 my ($dev2, $ino2) = stat('.'); 257 258 # Perl falls back to '.' if it can not determine '/' 259 if (($dev1 != $dev2) || ($ino1 != $ino2)) { 260 return $try; 261 } 262 # Fall back to UNIX format sys$disk. 263 return '/sys$disk/'; 264 } 265 return 'SYS$DISK:[000000]'; 266} 267 268=item tmpdir (override) 269 270Returns a string representation of the first writable directory 271from the following list or '' if none are writable: 272 273 /tmp if C<DECC$FILENAME_UNIX_REPORT> is enabled. 274 sys$scratch: 275 $ENV{TMPDIR} 276 277If running under taint mode, and if $ENV{TMPDIR} 278is tainted, it is not used. 279 280=cut 281 282sub tmpdir { 283 my $self = shift @_; 284 my $tmpdir = $self->_cached_tmpdir('TMPDIR'); 285 return $tmpdir if defined $tmpdir; 286 if ($self->_unix_rpt) { 287 $tmpdir = $self->_tmpdir('/tmp', '/sys$scratch', $ENV{TMPDIR}); 288 } 289 else { 290 $tmpdir = $self->_tmpdir( 'sys$scratch:', $ENV{TMPDIR} ); 291 } 292 $self->_cache_tmpdir($tmpdir, 'TMPDIR'); 293} 294 295=item updir (override) 296 297Returns a string representation of the parent directory: '[-]' or '..' 298 299=cut 300 301sub updir { 302 my $self = shift @_; 303 return '..' if ($self->_unix_rpt); 304 return '[-]'; 305} 306 307=item case_tolerant (override) 308 309VMS file specification syntax is case-tolerant. 310 311=cut 312 313sub case_tolerant { 314 return 1; 315} 316 317=item path (override) 318 319Translate logical name DCL$PATH as a searchlist, rather than trying 320to C<split> string value of C<$ENV{'PATH'}>. 321 322=cut 323 324sub path { 325 my (@dirs,$dir,$i); 326 while ($dir = $ENV{'DCL$PATH;' . $i++}) { push(@dirs,$dir); } 327 return @dirs; 328} 329 330=item file_name_is_absolute (override) 331 332Checks for VMS directory spec as well as Unix separators. 333 334=cut 335 336sub file_name_is_absolute { 337 my ($self,$file) = @_; 338 # If it's a logical name, expand it. 339 $file = $ENV{$file} while $file =~ /^[\w\$\-]+\Z(?!\n)/s && $ENV{$file}; 340 return scalar($file =~ m!^/!s || 341 $file =~ m![<\[][^.\-\]>]! || 342 $file =~ /^[A-Za-z0-9_\$\-\~]+(?<!\^):/); 343} 344 345=item splitpath (override) 346 347 ($volume,$directories,$file) = File::Spec->splitpath( $path ); 348 ($volume,$directories,$file) = File::Spec->splitpath( $path, 349 $no_file ); 350 351Passing a true value for C<$no_file> indicates that the path being 352split only contains directory components, even on systems where you 353can usually (when not supporting a foreign syntax) tell the difference 354between directories and files at a glance. 355 356=cut 357 358sub splitpath { 359 my($self,$path, $nofile) = @_; 360 my($dev,$dir,$file) = ('','',''); 361 my $vmsify_path = vmsify($path); 362 363 if ( $nofile ) { 364 #vmsify('d1/d2/d3') returns '[.d1.d2]d3' 365 #vmsify('/d1/d2/d3') returns 'd1:[d2]d3' 366 if( $vmsify_path =~ /(.*)\](.+)/ ){ 367 $vmsify_path = $1.'.'.$2.']'; 368 } 369 $vmsify_path =~ /(.+:)?(.*)/s; 370 $dir = defined $2 ? $2 : ''; # dir can be '0' 371 return ($1 || '',$dir,$file); 372 } 373 else { 374 $vmsify_path =~ /(.+:)?([\[<].*[\]>])?(.*)/s; 375 return ($1 || '',$2 || '',$3); 376 } 377} 378 379=item splitdir (override) 380 381Split a directory specification into the components. 382 383=cut 384 385sub splitdir { 386 my($self,$dirspec) = @_; 387 my @dirs = (); 388 return @dirs if ( (!defined $dirspec) || ('' eq $dirspec) ); 389 390 $dirspec =~ s/(?<!\^)</[/; # < and > ==> [ and ] 391 $dirspec =~ s/(?<!\^)>/]/; 392 $dirspec =~ s/(?<!\^)\]\[\./\.\]\[/g; # ][. ==> .][ 393 $dirspec =~ s/(?<!\^)\[000000\.\]\[/\[/g; # [000000.][ ==> [ 394 $dirspec =~ s/(?<!\^)\[000000\./\[/g; # [000000. ==> [ 395 $dirspec =~ s/(?<!\^)\.\]\[000000\]/\]/g; # .][000000] ==> ] 396 $dirspec =~ s/(?<!\^)\.\]\[/\./g; # foo.][bar ==> foo.bar 397 while ($dirspec =~ s/(^|[\[\<\.])\-(\-+)($|[\]\>\.])/$1-.$2$3/g) {} 398 # That loop does the following 399 # with any amount of dashes: 400 # .--. ==> .-.-. 401 # [--. ==> [-.-. 402 # .--] ==> .-.-] 403 # [--] ==> [-.-] 404 $dirspec = "[$dirspec]" unless $dirspec =~ /(?<!\^)[\[<]/; # make legal 405 $dirspec =~ s/^(\[|<)\./$1/; 406 @dirs = split /(?<!\^)\./, vmspath($dirspec); 407 $dirs[0] =~ s/^[\[<]//s; $dirs[-1] =~ s/[\]>]\Z(?!\n)//s; 408 @dirs; 409} 410 411 412=item catpath (override) 413 414Construct a complete filespec. 415 416=cut 417 418sub catpath { 419 my($self,$dev,$dir,$file) = @_; 420 421 # We look for a volume in $dev, then in $dir, but not both 422 my ($dir_volume, $dir_dir, $dir_file) = $self->splitpath($dir); 423 $dev = $dir_volume unless length $dev; 424 $dir = length $dir_file ? $self->catfile($dir_dir, $dir_file) : $dir_dir; 425 426 if ($dev =~ m|^(?<!\^)/+([^/]+)|) { $dev = "$1:"; } 427 else { $dev .= ':' unless $dev eq '' or $dev =~ /:\Z(?!\n)/; } 428 if (length($dev) or length($dir)) { 429 $dir = "[$dir]" unless $dir =~ /(?<!\^)[\[<\/]/; 430 $dir = vmspath($dir); 431 } 432 $dir = '' if length($dev) && ($dir eq '[]' || $dir eq '<>'); 433 "$dev$dir$file"; 434} 435 436=item abs2rel (override) 437 438Attempt to convert an absolute file specification to a relative specification. 439 440=cut 441 442sub abs2rel { 443 my $self = shift; 444 return vmspath(File::Spec::Unix::abs2rel( $self, @_ )) 445 if grep m{/}, @_; 446 447 my($path,$base) = @_; 448 $base = $self->_cwd() unless defined $base and length $base; 449 450 for ($path, $base) { $_ = $self->canonpath($_) } 451 452 # Are we even starting $path on the same (node::)device as $base? Note that 453 # logical paths or nodename differences may be on the "same device" 454 # but the comparison that ignores device differences so as to concatenate 455 # [---] up directory specs is not even a good idea in cases where there is 456 # a logical path difference between $path and $base nodename and/or device. 457 # Hence we fall back to returning the absolute $path spec 458 # if there is a case blind device (or node) difference of any sort 459 # and we do not even try to call $parse() or consult %ENV for $trnlnm() 460 # (this module needs to run on non VMS platforms after all). 461 462 my ($path_volume, $path_directories, $path_file) = $self->splitpath($path); 463 my ($base_volume, $base_directories, $base_file) = $self->splitpath($base); 464 return $path unless lc($path_volume) eq lc($base_volume); 465 466 for ($path, $base) { $_ = $self->rel2abs($_) } 467 468 # Now, remove all leading components that are the same 469 my @pathchunks = $self->splitdir( $path_directories ); 470 my $pathchunks = @pathchunks; 471 unshift(@pathchunks,'000000') unless $pathchunks[0] eq '000000'; 472 my @basechunks = $self->splitdir( $base_directories ); 473 my $basechunks = @basechunks; 474 unshift(@basechunks,'000000') unless $basechunks[0] eq '000000'; 475 476 while ( @pathchunks && 477 @basechunks && 478 lc( $pathchunks[0] ) eq lc( $basechunks[0] ) 479 ) { 480 shift @pathchunks ; 481 shift @basechunks ; 482 } 483 484 # @basechunks now contains the directories to climb out of, 485 # @pathchunks now has the directories to descend in to. 486 if ((@basechunks > 0) || ($basechunks != $pathchunks)) { 487 $path_directories = join '.', ('-' x @basechunks, @pathchunks) ; 488 } 489 else { 490 $path_directories = join '.', @pathchunks; 491 } 492 $path_directories = '['.$path_directories.']'; 493 return $self->canonpath( $self->catpath( '', $path_directories, $path_file ) ) ; 494} 495 496 497=item rel2abs (override) 498 499Return an absolute file specification from a relative one. 500 501=cut 502 503sub rel2abs { 504 my $self = shift ; 505 my ($path,$base ) = @_; 506 return undef unless defined $path; 507 if ($path =~ m/\//) { 508 $path = ( -d $path || $path =~ m/\/\z/ # educated guessing about 509 ? vmspath($path) # whether it's a directory 510 : vmsify($path) ); 511 } 512 $base = vmspath($base) if defined $base && $base =~ m/\//; 513 514 # Clean up and split up $path 515 if ( ! $self->file_name_is_absolute( $path ) ) { 516 # Figure out the effective $base and clean it up. 517 if ( !defined( $base ) || $base eq '' ) { 518 $base = $self->_cwd; 519 } 520 elsif ( ! $self->file_name_is_absolute( $base ) ) { 521 $base = $self->rel2abs( $base ) ; 522 } 523 else { 524 $base = $self->canonpath( $base ) ; 525 } 526 527 # Split up paths 528 my ( $path_directories, $path_file ) = 529 ($self->splitpath( $path ))[1,2] ; 530 531 my ( $base_volume, $base_directories ) = 532 $self->splitpath( $base ) ; 533 534 $path_directories = '' if $path_directories eq '[]' || 535 $path_directories eq '<>'; 536 my $sep = '' ; 537 $sep = '.' 538 if ( $base_directories =~ m{[^.\]>]\Z(?!\n)} && 539 $path_directories =~ m{^[^.\[<]}s 540 ) ; 541 $base_directories = "$base_directories$sep$path_directories"; 542 $base_directories =~ s{\.?[\]>][\[<]\.?}{.}; 543 544 $path = $self->catpath( $base_volume, $base_directories, $path_file ); 545 } 546 547 return $self->canonpath( $path ) ; 548} 549 550 551# eliminate_macros() and fixpath() are MakeMaker-specific methods 552# which are used inside catfile() and catdir(). MakeMaker has its own 553# copies as of 6.06_03 which are the canonical ones. We leave these 554# here, in peace, so that File::Spec continues to work with MakeMakers 555# prior to 6.06_03. 556# 557# Please consider these two methods deprecated. Do not patch them, 558# patch the ones in ExtUtils::MM_VMS instead. 559# 560# Update: MakeMaker 6.48 is still using these routines on VMS. 561# so they need to be kept up to date with ExtUtils::MM_VMS. 562 563sub eliminate_macros { 564 my($self,$path) = @_; 565 return '' unless (defined $path) && ($path ne ''); 566 $self = {} unless ref $self; 567 568 if ($path =~ /\s/) { 569 return join ' ', map { $self->eliminate_macros($_) } split /\s+/, $path; 570 } 571 572 my $npath = unixify($path); 573 # sometimes unixify will return a string with an off-by-one trailing null 574 $npath =~ s{\0$}{}; 575 576 my($complex) = 0; 577 my($head,$macro,$tail); 578 579 # perform m##g in scalar context so it acts as an iterator 580 while ($npath =~ m#(.*?)\$\((\S+?)\)(.*)#gs) { 581 if (defined $self->{$2}) { 582 ($head,$macro,$tail) = ($1,$2,$3); 583 if (ref $self->{$macro}) { 584 if (ref $self->{$macro} eq 'ARRAY') { 585 $macro = join ' ', @{$self->{$macro}}; 586 } 587 else { 588 print "Note: can't expand macro \$($macro) containing ",ref($self->{$macro}), 589 "\n\t(using MMK-specific deferred substitutuon; MMS will break)\n"; 590 $macro = "\cB$macro\cB"; 591 $complex = 1; 592 } 593 } 594 else { ($macro = unixify($self->{$macro})) =~ s#/\Z(?!\n)##; } 595 $npath = "$head$macro$tail"; 596 } 597 } 598 if ($complex) { $npath =~ s#\cB(.*?)\cB#\${$1}#gs; } 599 $npath; 600} 601 602# Deprecated. See the note above for eliminate_macros(). 603 604# Catchall routine to clean up problem MM[SK]/Make macros. Expands macros 605# in any directory specification, in order to avoid juxtaposing two 606# VMS-syntax directories when MM[SK] is run. Also expands expressions which 607# are all macro, so that we can tell how long the expansion is, and avoid 608# overrunning DCL's command buffer when MM[KS] is running. 609 610# fixpath() checks to see whether the result matches the name of a 611# directory in the current default directory and returns a directory or 612# file specification accordingly. C<$is_dir> can be set to true to 613# force fixpath() to consider the path to be a directory or false to force 614# it to be a file. 615 616sub fixpath { 617 my($self,$path,$force_path) = @_; 618 return '' unless $path; 619 $self = bless {}, $self unless ref $self; 620 my($fixedpath,$prefix,$name); 621 622 if ($path =~ /\s/) { 623 return join ' ', 624 map { $self->fixpath($_,$force_path) } 625 split /\s+/, $path; 626 } 627 628 if ($path =~ m#^\$\([^\)]+\)\Z(?!\n)#s || $path =~ m#[/:>\]]#) { 629 if ($force_path or $path =~ /(?:DIR\)|\])\Z(?!\n)/) { 630 $fixedpath = vmspath($self->eliminate_macros($path)); 631 } 632 else { 633 $fixedpath = vmsify($self->eliminate_macros($path)); 634 } 635 } 636 elsif ((($prefix,$name) = ($path =~ m#^\$\(([^\)]+)\)(.+)#s)) && $self->{$prefix}) { 637 my($vmspre) = $self->eliminate_macros("\$($prefix)"); 638 # is it a dir or just a name? 639 $vmspre = ($vmspre =~ m|/| or $prefix =~ /DIR\Z(?!\n)/) ? vmspath($vmspre) : ''; 640 $fixedpath = ($vmspre ? $vmspre : $self->{$prefix}) . $name; 641 $fixedpath = vmspath($fixedpath) if $force_path; 642 } 643 else { 644 $fixedpath = $path; 645 $fixedpath = vmspath($fixedpath) if $force_path; 646 } 647 # No hints, so we try to guess 648 if (!defined($force_path) and $fixedpath !~ /[:>(.\]]/) { 649 $fixedpath = vmspath($fixedpath) if -d $fixedpath; 650 } 651 652 # Trim off root dirname if it's had other dirs inserted in front of it. 653 $fixedpath =~ s/\.000000([\]>])/$1/; 654 # Special case for VMS absolute directory specs: these will have had device 655 # prepended during trip through Unix syntax in eliminate_macros(), since 656 # Unix syntax has no way to express "absolute from the top of this device's 657 # directory tree". 658 if ($path =~ /^[\[>][^.\-]/) { $fixedpath =~ s/^[^\[<]+//; } 659 $fixedpath; 660} 661 662 663=back 664 665=head1 COPYRIGHT 666 667Copyright (c) 2004 by the Perl 5 Porters. All rights reserved. 668 669This program is free software; you can redistribute it and/or modify 670it under the same terms as Perl itself. 671 672=head1 SEE ALSO 673 674See L<File::Spec> and L<File::Spec::Unix>. This package overrides the 675implementation of these methods, not the semantics. 676 677An explanation of VMS file specs can be found at 678L<http://h71000.www7.hp.com/doc/731FINAL/4506/4506pro_014.html#apps_locating_naming_files>. 679 680=cut 681 6821; 683