File Coverage

blib/lib/File/Spec/VMS.pm
Criterion Covered Total %
statement 117 205 57.0
branch 40 122 32.7
condition 16 74 21.6
subroutine 16 22 72.7
pod 16 16 100.0
total 205 439 46.7


line stmt bran cond sub pod time code
1             package File::Spec::VMS;
2              
3 2     2   2648 use strict;
  2         4  
  2         64  
4 2     2   11 use vars qw(@ISA $VERSION);
  2         3  
  2         182  
5             require File::Spec::Unix;
6              
7             $VERSION = '3.62';
8             $VERSION =~ tr/_//d;
9              
10             @ISA = qw(File::Spec::Unix);
11              
12 2     2   11 use File::Basename;
  2         3  
  2         192  
13 2     2   493 use VMS::Filespec;
  1         2  
  1         71  
14              
15             =head1 NAME
16              
17             File::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              
25             See File::Spec::Unix for a documentation of the methods provided
26             there. This package overrides the implementation of these methods, not
27             the semantics.
28              
29             The default behavior is to allow either VMS or Unix syntax on input and to
30             return VMS syntax on output unless Unix syntax has been explicitly requested
31             via the C 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              
40             my $use_feature;
41             BEGIN {
42 1 50   1   2 if (eval { local $SIG{__DIE__}; require VMS::Feature; }) {
  1         5  
  1         4393  
43 0         0 $use_feature = 1;
44             }
45             }
46              
47             # Need to look up the UNIX report mode. This may become a dynamic mode
48             # in the future.
49             sub _unix_rpt {
50 125     125   192 my $unix_rpt;
51 125 50       276 if ($use_feature) {
52 0         0 $unix_rpt = VMS::Feature::current("filename_unix_report");
53             } else {
54 125   50     554 my $env_unix_rpt = $ENV{'DECC$FILENAME_UNIX_REPORT'} || '';
55 125         246 $unix_rpt = $env_unix_rpt =~ /^[ET1]/i;
56             }
57 125         295 return $unix_rpt;
58             }
59              
60             =item canonpath (override)
61              
62             Removes redundant portions of file specifications and returns results
63             in native syntax unless Unix filename reporting has been enabled.
64              
65             =cut
66              
67              
68             sub canonpath {
69 85     85 1 24596 my($self,$path) = @_;
70              
71 85 50       219 return undef unless defined $path;
72              
73 85         206 my $unix_rpt = $self->_unix_rpt;
74              
75 85 100       292 if ($path =~ m|/|) {
76 1         4 my $pathify = $path =~ m|/\Z(?!\n)|;
77 1         16 $path = $self->SUPER::canonpath($path);
78              
79 1 50       6 return $path if $unix_rpt;
80 1 50       25 $path = $pathify ? vmspath($path) : vmsify($path);
81             }
82              
83 84         129 $path =~ s/(? ==> [ and ]
84 84         129 $path =~ s/(?/]/;
85 84         134 $path =~ s/(? .][
86 84         150 $path =~ s/(? [
87 84         118 $path =~ s/(? [
88 84         126 $path =~ s/(? ]
89 84         144 $path =~ s/(? foo.bar
90 84         258 1 while ($path =~ s/(?
91             # That loop does the following
92             # with any amount of dashes:
93             # .-.-. ==> .--.
94             # [-.-. ==> [--.
95             # .-.-] ==> .--]
96             # [-.-] ==> [--]
97 84         517 1 while ($path =~ s/(?
98             # That loop does the following
99             # with any amount (minimum 2)
100             # of dashes:
101             # .foo.--. ==> .-.
102             # .foo.--] ==> .-]
103             # [foo.--. ==> [-.
104             # [foo.--] ==> [-]
105             #
106             # And then, the remaining cases
107 84         133 $path =~ s/(? [-
108 84         157 $path =~ s/(? .
109 84         146 $path =~ s/(? [
110 84         150 $path =~ s/(? ]
111             # [foo.-] ==> [000000]
112 84         124 $path =~ s/(?
113             # [] ==>
114 84 50       278 $path =~ s/(?
115 84 50       623 return $unix_rpt ? unixify($path) : $path;
116             }
117              
118             =item catdir (override)
119              
120             Concatenates a list of file specifications, and returns the result as a
121             native directory specification unless the Unix filename reporting feature
122             has been enabled. No check is made for "impossible" cases (e.g. elements
123             other than the first being absolute filespecs).
124              
125             =cut
126              
127             sub catdir {
128 27     27 1 4692 my $self = shift;
129 27         45 my $dir = pop;
130              
131 27         50 my $unix_rpt = $self->_unix_rpt;
132              
133 27 50       51 my @dirs = grep {defined() && length()} @_;
  33         156  
134              
135 27         35 my $rslt;
136 27 100       50 if (@dirs) {
137 24 100       61 my $path = (@dirs == 1 ? $dirs[0] : $self->catdir(@dirs));
138 19         33 my ($spath,$sdir) = ($path,$dir);
139 19         30 $spath =~ s/\.dir\Z(?!\n)//i; $sdir =~ s/\.dir\Z(?!\n)//i;
  19         24  
140              
141 19 50       45 if ($unix_rpt) {
142 0 0       0 $spath = unixify($spath) unless $spath =~ m#/#;
143 0 0       0 $sdir= unixify($sdir) unless $sdir =~ m#/#;
144 0         0 return $self->SUPER::catdir($spath, $sdir)
145             }
146              
147 19         252 $rslt = vmspath( unixify($spath) . '/' . unixify($sdir));
148              
149             # Special case for VMS absolute directory specs: these will have
150             # had device prepended during trip through Unix syntax in
151             # eliminate_macros(), since Unix syntax has no way to express
152             # "absolute from the top of this device's directory tree".
153 0 0       0 if ($spath =~ /^[\[<][^.\-]/s) { $rslt =~ s/^[^\[<]+//s; }
  0         0  
154              
155             } else {
156             # Single directory. Return an empty string on null input; otherwise
157             # just return a canonical path.
158              
159 3 100 66     19 if (not defined $dir or not length $dir) {
160 1         3 $rslt = '';
161             } else {
162 2 50       28 $rslt = $unix_rpt ? $dir : vmspath($dir);
163             }
164             }
165 1         5 return $self->canonpath($rslt);
166             }
167              
168             =item catfile (override)
169              
170             Concatenates a list of directory specifications with a filename specification
171             to build a path.
172              
173             =cut
174              
175             sub catfile {
176 13     13 1 5062 my $self = shift;
177 13         21 my $tfile = pop();
178 13         34 my $file = $self->canonpath($tfile);
179 13 50       27 my @files = grep {defined() && length()} @_;
  21         103  
180              
181 13         29 my $unix_rpt = $self->_unix_rpt;
182              
183 13         14 my $rslt;
184 13 100       75 if (@files) {
185 11 100       38 my $path = (@files == 1 ? $files[0] : $self->catdir(@files));
186 1         2 my $spath = $path;
187              
188             # Something building a VMS path in pieces may try to pass a
189             # directory name in filename format, so normalize it.
190 1         4 $spath =~ s/\.dir\Z(?!\n)//i;
191              
192             # If the spath ends with a directory delimiter and the file is bare,
193             # then just concatenate them.
194 1 50 33     5 if ($spath =~ /^(?]+\)\Z(?!\n)/s && basename($file) eq $file) {
195 0         0 $rslt = "$spath$file";
196             } else {
197 1         14 $rslt = unixify($spath);
198 0 0 0     0 $rslt .= (defined($rslt) && length($rslt) ? '/' : '') . unixify($file);
199 0 0       0 $rslt = vmsify($rslt) unless $unix_rpt;
200             }
201             }
202             else {
203             # Only passed a single file?
204 2 50 33     12 my $xfile = (defined($file) && length($file)) ? $file : '';
205              
206 2 50       30 $rslt = $unix_rpt ? $xfile : vmsify($xfile);
207             }
208 0 0       0 return $self->canonpath($rslt) unless $unix_rpt;
209              
210             # In Unix report mode, do not strip off redundant path information.
211 0         0 return $rslt;
212             }
213              
214              
215             =item curdir (override)
216              
217             Returns a string representation of the current directory: '[]' or '.'
218              
219             =cut
220              
221             sub curdir {
222 0     0 1 0 my $self = shift @_;
223 0 0       0 return '.' if ($self->_unix_rpt);
224 0         0 return '[]';
225             }
226              
227             =item devnull (override)
228              
229             Returns a string representation of the null device: '_NLA0:' or '/dev/null'
230              
231             =cut
232              
233             sub devnull {
234 0     0 1 0 my $self = shift @_;
235 0 0       0 return '/dev/null' if ($self->_unix_rpt);
236 0         0 return "_NLA0:";
237             }
238              
239             =item rootdir (override)
240              
241             Returns a string representation of the root directory: 'SYS$DISK:[000000]'
242             or '/'
243              
244             =cut
245              
246             sub rootdir {
247 0     0 1 0 my $self = shift @_;
248 0 0       0 if ($self->_unix_rpt) {
249             # Root may exist, try it first.
250 0         0 my $try = '/';
251 0         0 my ($dev1, $ino1) = stat('/');
252 0         0 my ($dev2, $ino2) = stat('.');
253              
254             # Perl falls back to '.' if it can not determine '/'
255 0 0 0     0 if (($dev1 != $dev2) || ($ino1 != $ino2)) {
256 0         0 return $try;
257             }
258             # Fall back to UNIX format sys$disk.
259 0         0 return '/sys$disk/';
260             }
261 0         0 return 'SYS$DISK:[000000]';
262             }
263              
264             =item tmpdir (override)
265              
266             Returns a string representation of the first writable directory
267             from the following list or '' if none are writable:
268              
269             /tmp if C is enabled.
270             sys$scratch:
271             $ENV{TMPDIR}
272              
273             If running under taint mode, and if $ENV{TMPDIR}
274             is tainted, it is not used.
275              
276             =cut
277              
278             sub tmpdir {
279 0     0 1 0 my $self = shift @_;
280 0         0 my $tmpdir = $self->_cached_tmpdir('TMPDIR');
281 0 0       0 return $tmpdir if defined $tmpdir;
282 0 0       0 if ($self->_unix_rpt) {
283 0         0 $tmpdir = $self->_tmpdir('/tmp', '/sys$scratch', $ENV{TMPDIR});
284             }
285             else {
286 0         0 $tmpdir = $self->_tmpdir( 'sys$scratch:', $ENV{TMPDIR} );
287             }
288 0         0 $self->_cache_tmpdir($tmpdir, 'TMPDIR');
289             }
290              
291             =item updir (override)
292              
293             Returns a string representation of the parent directory: '[-]' or '..'
294              
295             =cut
296              
297             sub updir {
298 0     0 1 0 my $self = shift @_;
299 0 0       0 return '..' if ($self->_unix_rpt);
300 0         0 return '[-]';
301             }
302              
303             =item case_tolerant (override)
304              
305             VMS file specification syntax is case-tolerant.
306              
307             =cut
308              
309             sub case_tolerant {
310 1     1 1 609 return 1;
311             }
312              
313             =item path (override)
314              
315             Translate logical name DCL$PATH as a searchlist, rather than trying
316             to C string value of C<$ENV{'PATH'}>.
317              
318             =cut
319              
320             sub path {
321 0     0 1 0 my (@dirs,$dir,$i);
322 0         0 while ($dir = $ENV{'DCL$PATH;' . $i++}) { push(@dirs,$dir); }
  0         0  
323 0         0 return @dirs;
324             }
325              
326             =item file_name_is_absolute (override)
327              
328             Checks for VMS directory spec as well as Unix separators.
329              
330             =cut
331              
332             sub file_name_is_absolute {
333 46     46 1 1760 my ($self,$file) = @_;
334             # If it's a logical name, expand it.
335 46   33     180 $file = $ENV{$file} while $file =~ /^[\w\$\-]+\Z(?!\n)/s && $ENV{$file};
336 46   100     424 return scalar($file =~ m!^/!s ||
337             $file =~ m![<\[][^.\-\]>]! ||
338             $file =~ /^[A-Za-z0-9_\$\-\~]+(?
339             }
340              
341             =item splitpath (override)
342              
343             ($volume,$directories,$file) = File::Spec->splitpath( $path );
344             ($volume,$directories,$file) = File::Spec->splitpath( $path,
345             $no_file );
346              
347             Passing a true value for C<$no_file> indicates that the path being
348             split only contains directory components, even on systems where you
349             can usually (when not supporting a foreign syntax) tell the difference
350             between directories and files at a glance.
351              
352             =cut
353              
354             sub splitpath {
355 81     81 1 17095 my($self,$path, $nofile) = @_;
356 81         214 my($dev,$dir,$file) = ('','','');
357 81         1097 my $vmsify_path = vmsify($path);
358              
359 0 0       0 if ( $nofile ) {
360             #vmsify('d1/d2/d3') returns '[.d1.d2]d3'
361             #vmsify('/d1/d2/d3') returns 'd1:[d2]d3'
362 0 0       0 if( $vmsify_path =~ /(.*)\](.+)/ ){
363 0         0 $vmsify_path = $1.'.'.$2.']';
364             }
365 0         0 $vmsify_path =~ /(.+:)?(.*)/s;
366 0 0       0 $dir = defined $2 ? $2 : ''; # dir can be '0'
367 0   0     0 return ($1 || '',$dir,$file);
368             }
369             else {
370 0         0 $vmsify_path =~ /(.+:)?([\[<].*[\]>])?(.*)/s;
371 0   0     0 return ($1 || '',$2 || '',$3);
      0        
372             }
373             }
374              
375             =item splitdir (override)
376              
377             Split a directory specification into the components.
378              
379             =cut
380              
381             sub splitdir {
382 15     15 1 6262 my($self,$dirspec) = @_;
383 15         27 my @dirs = ();
384 15 100 66     100 return @dirs if ( (!defined $dirspec) || ('' eq $dirspec) );
385              
386 14         25 $dirspec =~ s/(? ==> [ and ]
387 14         21 $dirspec =~ s/(?/]/;
388 14         21 $dirspec =~ s/(? .][
389 14         24 $dirspec =~ s/(? [
390 14         24 $dirspec =~ s/(? [
391 14         18 $dirspec =~ s/(? ]
392 14         24 $dirspec =~ s/(? foo.bar
393 14         65 while ($dirspec =~ s/(^|[\[\<\.])\-(\-+)($|[\]\>\.])/$1-.$2$3/g) {}
394             # That loop does the following
395             # with any amount of dashes:
396             # .--. ==> .-.-.
397             # [--. ==> [-.-.
398             # .--] ==> .-.-]
399             # [--] ==> [-.-]
400 14 100       105 $dirspec = "[$dirspec]" unless $dirspec =~ /(?
401 14         42 $dirspec =~ s/^(\[|<)\./$1/;
402 14         204 @dirs = split /(?
403 0         0 $dirs[0] =~ s/^[\[<]//s; $dirs[-1] =~ s/[\]>]\Z(?!\n)//s;
  0         0  
404 0         0 @dirs;
405             }
406              
407              
408             =item catpath (override)
409              
410             Construct a complete filespec.
411              
412             =cut
413              
414             sub catpath {
415 13     13 1 5038 my($self,$dev,$dir,$file) = @_;
416            
417             # We look for a volume in $dev, then in $dir, but not both
418 13         43 my ($dir_volume, $dir_dir, $dir_file) = $self->splitpath($dir);
419 0 0       0 $dev = $dir_volume unless length $dev;
420 0 0       0 $dir = length $dir_file ? $self->catfile($dir_dir, $dir_file) : $dir_dir;
421            
422 0 0       0 if ($dev =~ m|^(?
  0         0  
423 0 0 0     0 else { $dev .= ':' unless $dev eq '' or $dev =~ /:\Z(?!\n)/; }
424 0 0 0     0 if (length($dev) or length($dir)) {
425 0 0       0 $dir = "[$dir]" unless $dir =~ /(?
426 0         0 $dir = vmspath($dir);
427             }
428 0 0 0     0 $dir = '' if length($dev) && ($dir eq '[]' || $dir eq '<>');
      0        
429 0         0 "$dev$dir$file";
430             }
431              
432             =item abs2rel (override)
433              
434             Attempt to convert an absolute file specification to a relative specification.
435              
436             =cut
437              
438             sub abs2rel {
439 16     16 1 6775 my $self = shift;
440 16 50 33     91 return vmspath(File::Spec::Unix::abs2rel( $self, @_ ))
441             if ((grep m{/}, @_) && !(grep m{(?
442              
443 16         32 my($path,$base) = @_;
444 16 50 33     95 $base = $self->_cwd() unless defined $base and length $base;
445              
446             # If there is no device or directory syntax on $base, make sure it
447             # is treated as a directory.
448 16 50       67 $base = VMS::Filespec::vmspath($base) unless $base =~ m{(?
449              
450 16         36 for ($path, $base) { $_ = $self->rel2abs($_) }
  32         95  
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 16         46 my ($path_volume, $path_directories, $path_file) = $self->splitpath($path);
463 0         0 my ($base_volume, $base_directories, $base_file) = $self->splitpath($base);
464 0 0       0 return $path unless lc($path_volume) eq lc($base_volume);
465              
466             # Now, remove all leading components that are the same
467 0         0 my @pathchunks = $self->splitdir( $path_directories );
468 0         0 my $pathchunks = @pathchunks;
469 0 0       0 unshift(@pathchunks,'000000') unless $pathchunks[0] eq '000000';
470 0         0 my @basechunks = $self->splitdir( $base_directories );
471 0         0 my $basechunks = @basechunks;
472 0 0       0 unshift(@basechunks,'000000') unless $basechunks[0] eq '000000';
473              
474 0   0     0 while ( @pathchunks &&
      0        
475             @basechunks &&
476             lc( $pathchunks[0] ) eq lc( $basechunks[0] )
477             ) {
478 0         0 shift @pathchunks ;
479 0         0 shift @basechunks ;
480             }
481              
482             # @basechunks now contains the directories to climb out of,
483             # @pathchunks now has the directories to descend in to.
484 0 0 0     0 if ((@basechunks > 0) || ($basechunks != $pathchunks)) {
485 0         0 $path_directories = join '.', ('-' x @basechunks, @pathchunks) ;
486             }
487             else {
488 0         0 $path_directories = join '.', @pathchunks;
489             }
490 0         0 $path_directories = '['.$path_directories.']';
491 0         0 return $self->canonpath( $self->catpath( '', $path_directories, $path_file ) ) ;
492             }
493              
494              
495             =item rel2abs (override)
496              
497             Return an absolute file specification from a relative one.
498              
499             =cut
500              
501             sub rel2abs {
502 38     38 1 2733 my $self = shift ;
503 38         64 my ($path,$base ) = @_;
504 38 50       92 return undef unless defined $path;
505 38 50       142 if ($path =~ m/\//) {
506 0 0 0     0 $path = ( -d $path || $path =~ m/\/\z/ # educated guessing about
507             ? vmspath($path) # whether it's a directory
508             : vmsify($path) );
509             }
510 38 50 66     127 $base = vmspath($base) if defined $base && $base =~ m/\//;
511              
512             # Clean up and split up $path
513 38 100       97 if ( ! $self->file_name_is_absolute( $path ) ) {
514             # Figure out the effective $base and clean it up.
515 5 50 33     36 if ( !defined( $base ) || $base eq '' ) {
    50          
516 0         0 $base = $self->_cwd;
517             }
518             elsif ( ! $self->file_name_is_absolute( $base ) ) {
519 0         0 $base = $self->rel2abs( $base ) ;
520             }
521             else {
522 5         16 $base = $self->canonpath( $base ) ;
523             }
524              
525             # Split up paths
526 5         20 my ( $path_directories, $path_file ) =
527             ($self->splitpath( $path ))[1,2] ;
528              
529 0         0 my ( $base_volume, $base_directories ) =
530             $self->splitpath( $base ) ;
531              
532 0 0 0     0 $path_directories = '' if $path_directories eq '[]' ||
533             $path_directories eq '<>';
534 0         0 my $sep = '' ;
535 0 0 0     0 $sep = '.'
536             if ( $base_directories =~ m{[^.\]>]\Z(?!\n)} &&
537             $path_directories =~ m{^[^.\[<]}s
538             ) ;
539 0         0 $base_directories = "$base_directories$sep$path_directories";
540 0         0 $base_directories =~ s{\.?[\]>][\[<]\.?}{.};
541              
542 0         0 $path = $self->catpath( $base_volume, $base_directories, $path_file );
543             }
544              
545 33         92 return $self->canonpath( $path ) ;
546             }
547              
548              
549             =back
550              
551             =head1 COPYRIGHT
552              
553             Copyright (c) 2004-14 by the Perl 5 Porters. All rights reserved.
554              
555             This program is free software; you can redistribute it and/or modify
556             it under the same terms as Perl itself.
557              
558             =head1 SEE ALSO
559              
560             See L and L. This package overrides the
561             implementation of these methods, not the semantics.
562              
563             An explanation of VMS file specs can be found at
564             L.
565              
566             =cut
567              
568             1;