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