1package File::Spec::VMS; 2 3use strict; 4use Cwd (); 5require File::Spec::Unix; 6 7our $VERSION = '3.84'; 8$VERSION =~ tr/_//d; 9 10our @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 $rslt = vmspath( unixify($spath) . '/' . unixify($sdir)); 151 152 # Special case for VMS absolute directory specs: these will have 153 # had device prepended during trip through Unix syntax in 154 # eliminate_macros(), since Unix syntax has no way to express 155 # "absolute from the top of this device's directory tree". 156 if ($spath =~ /^[\[<][^.\-]/s) { $rslt =~ s/^[^\[<]+//s; } 157 158 } else { 159 # Single directory. Return an empty string on null input; otherwise 160 # just return a canonical path. 161 162 if (not defined $dir or not length $dir) { 163 $rslt = ''; 164 } else { 165 $rslt = $unix_rpt ? $dir : vmspath($dir); 166 } 167 } 168 return $self->canonpath($rslt); 169} 170 171=item catfile (override) 172 173Concatenates a list of directory specifications with a filename specification 174to build a path. 175 176=cut 177 178sub catfile { 179 my $self = shift; 180 my $tfile = pop(); 181 my $file = $self->canonpath($tfile); 182 my @files = grep {defined() && length()} @_; 183 184 my $unix_rpt = $self->_unix_rpt; 185 186 my $rslt; 187 if (@files) { 188 my $path = (@files == 1 ? $files[0] : $self->catdir(@files)); 189 my $spath = $path; 190 191 # Something building a VMS path in pieces may try to pass a 192 # directory name in filename format, so normalize it. 193 $spath =~ s/\.dir\Z(?!\n)//i; 194 195 # If the spath ends with a directory delimiter and the file is bare, 196 # then just concatenate them. 197 if ($spath =~ /^(?<!\^)[^\)\]\/:>]+\)\Z(?!\n)/s && basename($file) eq $file) { 198 $rslt = "$spath$file"; 199 } else { 200 $rslt = unixify($spath); 201 $rslt .= (defined($rslt) && length($rslt) ? '/' : '') . unixify($file); 202 $rslt = vmsify($rslt) unless $unix_rpt; 203 } 204 } 205 else { 206 # Only passed a single file? 207 my $xfile = (defined($file) && length($file)) ? $file : ''; 208 209 $rslt = $unix_rpt ? $xfile : vmsify($xfile); 210 } 211 return $self->canonpath($rslt) unless $unix_rpt; 212 213 # In Unix report mode, do not strip off redundant path information. 214 return $rslt; 215} 216 217 218=item curdir (override) 219 220Returns a string representation of the current directory: '[]' or '.' 221 222=cut 223 224sub curdir { 225 my $self = shift @_; 226 return '.' if ($self->_unix_rpt); 227 return '[]'; 228} 229 230=item devnull (override) 231 232Returns a string representation of the null device: '_NLA0:' or '/dev/null' 233 234=cut 235 236sub devnull { 237 my $self = shift @_; 238 return '/dev/null' if ($self->_unix_rpt); 239 return "_NLA0:"; 240} 241 242=item rootdir (override) 243 244Returns a string representation of the root directory: 'SYS$DISK:[000000]' 245or '/' 246 247=cut 248 249sub rootdir { 250 my $self = shift @_; 251 if ($self->_unix_rpt) { 252 # Root may exist, try it first. 253 my $try = '/'; 254 my ($dev1, $ino1) = stat('/'); 255 my ($dev2, $ino2) = stat('.'); 256 257 # Perl falls back to '.' if it can not determine '/' 258 if (($dev1 != $dev2) || ($ino1 != $ino2)) { 259 return $try; 260 } 261 # Fall back to UNIX format sys$disk. 262 return '/sys$disk/'; 263 } 264 return 'SYS$DISK:[000000]'; 265} 266 267=item tmpdir (override) 268 269Returns a string representation of the first writable directory 270from the following list or '' if none are writable: 271 272 /tmp if C<DECC$FILENAME_UNIX_REPORT> is enabled. 273 sys$scratch: 274 $ENV{TMPDIR} 275 276If running under taint mode, and if $ENV{TMPDIR} 277is tainted, it is not used. 278 279=cut 280 281sub tmpdir { 282 my $self = shift @_; 283 my $tmpdir = $self->_cached_tmpdir('TMPDIR'); 284 return $tmpdir if defined $tmpdir; 285 if ($self->_unix_rpt) { 286 $tmpdir = $self->_tmpdir('/tmp', '/sys$scratch', $ENV{TMPDIR}); 287 } 288 else { 289 $tmpdir = $self->_tmpdir( 'sys$scratch:', $ENV{TMPDIR} ); 290 } 291 $self->_cache_tmpdir($tmpdir, 'TMPDIR'); 292} 293 294=item updir (override) 295 296Returns a string representation of the parent directory: '[-]' or '..' 297 298=cut 299 300sub updir { 301 my $self = shift @_; 302 return '..' if ($self->_unix_rpt); 303 return '[-]'; 304} 305 306=item case_tolerant (override) 307 308VMS file specification syntax is case-tolerant. 309 310=cut 311 312sub case_tolerant { 313 return 1; 314} 315 316=item path (override) 317 318Translate logical name DCL$PATH as a searchlist, rather than trying 319to C<split> string value of C<$ENV{'PATH'}>. 320 321=cut 322 323sub path { 324 my (@dirs,$dir,$i); 325 while ($dir = $ENV{'DCL$PATH;' . $i++}) { push(@dirs,$dir); } 326 return @dirs; 327} 328 329=item file_name_is_absolute (override) 330 331Checks for VMS directory spec as well as Unix separators. 332 333=cut 334 335sub file_name_is_absolute { 336 my ($self,$file) = @_; 337 # If it's a logical name, expand it. 338 $file = $ENV{$file} while $file =~ /^[\w\$\-]+\Z(?!\n)/s && $ENV{$file}; 339 return scalar($file =~ m!^/!s || 340 $file =~ m![<\[][^.\-\]>]! || 341 $file =~ /^[A-Za-z0-9_\$\-\~]+(?<!\^):/); 342} 343 344=item splitpath (override) 345 346 ($volume,$directories,$file) = File::Spec->splitpath( $path ); 347 ($volume,$directories,$file) = File::Spec->splitpath( $path, 348 $no_file ); 349 350Passing a true value for C<$no_file> indicates that the path being 351split only contains directory components, even on systems where you 352can usually (when not supporting a foreign syntax) tell the difference 353between directories and files at a glance. 354 355=cut 356 357sub splitpath { 358 my($self,$path, $nofile) = @_; 359 my($dev,$dir,$file) = ('','',''); 360 my $vmsify_path = vmsify($path); 361 362 if ( $nofile ) { 363 #vmsify('d1/d2/d3') returns '[.d1.d2]d3' 364 #vmsify('/d1/d2/d3') returns 'd1:[d2]d3' 365 if( $vmsify_path =~ /(.*)\](.+)/ ){ 366 $vmsify_path = $1.'.'.$2.']'; 367 } 368 $vmsify_path =~ /(.+:)?(.*)/s; 369 $dir = defined $2 ? $2 : ''; # dir can be '0' 370 return ($1 || '',$dir,$file); 371 } 372 else { 373 $vmsify_path =~ /(.+:)?([\[<].*[\]>])?(.*)/s; 374 return ($1 || '',$2 || '',$3); 375 } 376} 377 378=item splitdir (override) 379 380Split a directory specification into the components. 381 382=cut 383 384sub splitdir { 385 my($self,$dirspec) = @_; 386 my @dirs = (); 387 return @dirs if ( (!defined $dirspec) || ('' eq $dirspec) ); 388 389 $dirspec =~ s/(?<!\^)</[/; # < and > ==> [ and ] 390 $dirspec =~ s/(?<!\^)>/]/; 391 $dirspec =~ s/(?<!\^)\]\[\./\.\]\[/g; # ][. ==> .][ 392 $dirspec =~ s/(?<!\^)\[000000\.\]\[/\[/g; # [000000.][ ==> [ 393 $dirspec =~ s/(?<!\^)\[000000\./\[/g; # [000000. ==> [ 394 $dirspec =~ s/(?<!\^)\.\]\[000000\]/\]/g; # .][000000] ==> ] 395 $dirspec =~ s/(?<!\^)\.\]\[/\./g; # foo.][bar ==> foo.bar 396 while ($dirspec =~ s/(^|[\[\<\.])\-(\-+)($|[\]\>\.])/$1-.$2$3/g) {} 397 # That loop does the following 398 # with any amount of dashes: 399 # .--. ==> .-.-. 400 # [--. ==> [-.-. 401 # .--] ==> .-.-] 402 # [--] ==> [-.-] 403 $dirspec = "[$dirspec]" unless $dirspec =~ /(?<!\^)[\[<]/; # make legal 404 $dirspec =~ s/^(\[|<)\./$1/; 405 @dirs = split /(?<!\^)\./, vmspath($dirspec); 406 $dirs[0] =~ s/^[\[<]//s; $dirs[-1] =~ s/[\]>]\Z(?!\n)//s; 407 @dirs; 408} 409 410 411=item catpath (override) 412 413Construct a complete filespec. 414 415=cut 416 417sub catpath { 418 my($self,$dev,$dir,$file) = @_; 419 420 # We look for a volume in $dev, then in $dir, but not both 421 my ($dir_volume, $dir_dir, $dir_file) = $self->splitpath($dir); 422 $dev = $dir_volume unless length $dev; 423 $dir = length $dir_file ? $self->catfile($dir_dir, $dir_file) : $dir_dir; 424 425 if ($dev =~ m|^(?<!\^)/+([^/]+)|) { $dev = "$1:"; } 426 else { $dev .= ':' unless $dev eq '' or $dev =~ /:\Z(?!\n)/; } 427 if (length($dev) or length($dir)) { 428 $dir = "[$dir]" unless $dir =~ /(?<!\^)[\[<\/]/; 429 $dir = vmspath($dir); 430 } 431 $dir = '' if length($dev) && ($dir eq '[]' || $dir eq '<>'); 432 "$dev$dir$file"; 433} 434 435=item abs2rel (override) 436 437Attempt to convert an absolute file specification to a relative specification. 438 439=cut 440 441sub abs2rel { 442 my $self = shift; 443 my($path,$base) = @_; 444 445 $base = Cwd::getcwd() unless defined $base and length $base; 446 447 # If there is no device or directory syntax on $base, make sure it 448 # is treated as a directory. 449 $base = vmspath($base) unless $base =~ m{(?<!\^)[\[<:]}; 450 451 for ($path, $base) { $_ = $self->rel2abs($_) } 452 453 # Are we even starting $path on the same (node::)device as $base? Note that 454 # logical paths or nodename differences may be on the "same device" 455 # but the comparison that ignores device differences so as to concatenate 456 # [---] up directory specs is not even a good idea in cases where there is 457 # a logical path difference between $path and $base nodename and/or device. 458 # Hence we fall back to returning the absolute $path spec 459 # if there is a case blind device (or node) difference of any sort 460 # and we do not even try to call $parse() or consult %ENV for $trnlnm() 461 # (this module needs to run on non VMS platforms after all). 462 463 my ($path_volume, $path_directories, $path_file) = $self->splitpath($path); 464 my ($base_volume, $base_directories, $base_file) = $self->splitpath($base); 465 return $self->canonpath( $path ) unless lc($path_volume) eq lc($base_volume); 466 467 # Now, remove all leading components that are the same 468 my @pathchunks = $self->splitdir( $path_directories ); 469 my $pathchunks = @pathchunks; 470 unshift(@pathchunks,'000000') unless $pathchunks[0] eq '000000'; 471 my @basechunks = $self->splitdir( $base_directories ); 472 my $basechunks = @basechunks; 473 unshift(@basechunks,'000000') unless $basechunks[0] eq '000000'; 474 475 while ( @pathchunks && 476 @basechunks && 477 lc( $pathchunks[0] ) eq lc( $basechunks[0] ) 478 ) { 479 shift @pathchunks ; 480 shift @basechunks ; 481 } 482 483 # @basechunks now contains the directories to climb out of, 484 # @pathchunks now has the directories to descend in to. 485 if ((@basechunks > 0) || ($basechunks != $pathchunks)) { 486 $path_directories = join '.', ('-' x @basechunks, @pathchunks) ; 487 } 488 else { 489 $path_directories = join '.', @pathchunks; 490 } 491 $path_directories = '['.$path_directories.']'; 492 return $self->canonpath( $self->catpath( '', $path_directories, $path_file ) ) ; 493} 494 495 496=item rel2abs (override) 497 498Return an absolute file specification from a relative one. 499 500=cut 501 502sub rel2abs { 503 my $self = shift ; 504 my ($path,$base ) = @_; 505 return undef unless defined $path; 506 if ($path =~ m/\//) { 507 $path = ( -d $path || $path =~ m/\/\z/ # educated guessing about 508 ? vmspath($path) # whether it's a directory 509 : vmsify($path) ); 510 } 511 $base = vmspath($base) if defined $base && $base =~ m/\//; 512 513 # Clean up and split up $path 514 if ( ! $self->file_name_is_absolute( $path ) ) { 515 # Figure out the effective $base and clean it up. 516 if ( !defined( $base ) || $base eq '' ) { 517 $base = Cwd::getcwd(); 518 } 519 elsif ( ! $self->file_name_is_absolute( $base ) ) { 520 $base = $self->rel2abs( $base ) ; 521 } 522 else { 523 $base = $self->canonpath( $base ) ; 524 } 525 526 # Split up paths 527 my ( $path_directories, $path_file ) = 528 ($self->splitpath( $path ))[1,2] ; 529 530 my ( $base_volume, $base_directories ) = 531 $self->splitpath( $base ) ; 532 533 $path_directories = '' if $path_directories eq '[]' || 534 $path_directories eq '<>'; 535 my $sep = '' ; 536 $sep = '.' 537 if ( $base_directories =~ m{[^.\]>]\Z(?!\n)} && 538 $path_directories =~ m{^[^.\[<]}s 539 ) ; 540 $base_directories = "$base_directories$sep$path_directories"; 541 $base_directories =~ s{\.?[\]>][\[<]\.?}{.}; 542 543 $path = $self->catpath( $base_volume, $base_directories, $path_file ); 544 } 545 546 return $self->canonpath( $path ) ; 547} 548 549 550=back 551 552=head1 COPYRIGHT 553 554Copyright (c) 2004-14 by the Perl 5 Porters. All rights reserved. 555 556This program is free software; you can redistribute it and/or modify 557it under the same terms as Perl itself. 558 559=head1 SEE ALSO 560 561See L<File::Spec> and L<File::Spec::Unix>. This package overrides the 562implementation of these methods, not the semantics. 563 564An explanation of VMS file specs can be found at 565L<http://h71000.www7.hp.com/doc/731FINAL/4506/4506pro_014.html#apps_locating_naming_files>. 566 567=cut 568 5691; 570