1package File::Spec::Unix;
2
3use strict;
4use Cwd ();
5
6our $VERSION = '3.78';
7$VERSION =~ tr/_//d;
8
9=head1 NAME
10
11File::Spec::Unix - File::Spec for Unix, base for other File::Spec modules
12
13=head1 SYNOPSIS
14
15 require File::Spec::Unix; # Done automatically by File::Spec
16
17=head1 DESCRIPTION
18
19Methods for manipulating file specifications.  Other File::Spec
20modules, such as File::Spec::Mac, inherit from File::Spec::Unix and
21override specific methods.
22
23=head1 METHODS
24
25=over 2
26
27=item canonpath()
28
29No physical check on the filesystem, but a logical cleanup of a
30path. On UNIX eliminates successive slashes and successive "/.".
31
32    $cpath = File::Spec->canonpath( $path ) ;
33
34Note that this does *not* collapse F<x/../y> sections into F<y>.  This
35is by design.  If F</foo> on your system is a symlink to F</bar/baz>,
36then F</foo/../quux> is actually F</bar/quux>, not F</quux> as a naive
37F<../>-removal would give you.  If you want to do this kind of
38processing, you probably want C<Cwd>'s C<realpath()> function to
39actually traverse the filesystem cleaning up paths like this.
40
41=cut
42
43sub _pp_canonpath {
44    my ($self,$path) = @_;
45    return unless defined $path;
46
47    # Handle POSIX-style node names beginning with double slash (qnx, nto)
48    # (POSIX says: "a pathname that begins with two successive slashes
49    # may be interpreted in an implementation-defined manner, although
50    # more than two leading slashes shall be treated as a single slash.")
51    my $node = '';
52    my $double_slashes_special = $^O eq 'qnx' || $^O eq 'nto';
53
54
55    if ( $double_slashes_special
56         && ( $path =~ s{^(//[^/]+)/?\z}{}s || $path =~ s{^(//[^/]+)/}{/}s ) ) {
57      $node = $1;
58    }
59    # This used to be
60    # $path =~ s|/+|/|g unless ($^O eq 'cygwin');
61    # but that made tests 29, 30, 35, 46, and 213 (as of #13272) to fail
62    # (Mainly because trailing "" directories didn't get stripped).
63    # Why would cygwin avoid collapsing multiple slashes into one? --jhi
64    $path =~ s|/{2,}|/|g;                            # xx////xx  -> xx/xx
65    $path =~ s{(?:/\.)+(?:/|\z)}{/}g;                # xx/././xx -> xx/xx
66    $path =~ s|^(?:\./)+||s unless $path eq "./";    # ./xx      -> xx
67    $path =~ s|^/(?:\.\./)+|/|;                      # /../../xx -> xx
68    $path =~ s|^/\.\.$|/|;                         # /..       -> /
69    $path =~ s|/\z|| unless $path eq "/";          # xx/       -> xx
70    return "$node$path";
71}
72*canonpath = \&_pp_canonpath unless defined &canonpath;
73
74=item catdir()
75
76Concatenate two or more directory names to form a complete path ending
77with a directory. But remove the trailing slash from the resulting
78string, because it doesn't look good, isn't necessary and confuses
79OS2. Of course, if this is the root directory, don't cut off the
80trailing slash :-)
81
82=cut
83
84sub _pp_catdir {
85    my $self = shift;
86
87    $self->canonpath(join('/', @_, '')); # '' because need a trailing '/'
88}
89*catdir = \&_pp_catdir unless defined &catdir;
90
91=item catfile
92
93Concatenate one or more directory names and a filename to form a
94complete path ending with a filename
95
96=cut
97
98sub _pp_catfile {
99    my $self = shift;
100    my $file = $self->canonpath(pop @_);
101    return $file unless @_;
102    my $dir = $self->catdir(@_);
103    $dir .= "/" unless substr($dir,-1) eq "/";
104    return $dir.$file;
105}
106*catfile = \&_pp_catfile unless defined &catfile;
107
108=item curdir
109
110Returns a string representation of the current directory.  "." on UNIX.
111
112=cut
113
114sub curdir { '.' }
115use constant _fn_curdir => ".";
116
117=item devnull
118
119Returns a string representation of the null device. "/dev/null" on UNIX.
120
121=cut
122
123sub devnull { '/dev/null' }
124use constant _fn_devnull => "/dev/null";
125
126=item rootdir
127
128Returns a string representation of the root directory.  "/" on UNIX.
129
130=cut
131
132sub rootdir { '/' }
133use constant _fn_rootdir => "/";
134
135=item tmpdir
136
137Returns a string representation of the first writable directory from
138the following list or the current directory if none from the list are
139writable:
140
141    $ENV{TMPDIR}
142    /tmp
143
144If running under taint mode, and if $ENV{TMPDIR}
145is tainted, it is not used.
146
147=cut
148
149my ($tmpdir, %tmpenv);
150# Cache and return the calculated tmpdir, recording which env vars
151# determined it.
152sub _cache_tmpdir {
153    @tmpenv{@_[2..$#_]} = @ENV{@_[2..$#_]};
154    return $tmpdir = $_[1];
155}
156# Retrieve the cached tmpdir, checking first whether relevant env vars have
157# changed and invalidated the cache.
158sub _cached_tmpdir {
159    shift;
160    local $^W;
161    return if grep $ENV{$_} ne $tmpenv{$_}, @_;
162    return $tmpdir;
163}
164sub _tmpdir {
165    my $self = shift;
166    my @dirlist = @_;
167    my $taint = do { no strict 'refs'; ${"\cTAINT"} };
168    if ($taint) { # Check for taint mode on perl >= 5.8.0
169	require Scalar::Util;
170	@dirlist = grep { ! Scalar::Util::tainted($_) } @dirlist;
171    }
172    elsif ($] < 5.007) { # No ${^TAINT} before 5.8
173	@dirlist = grep { !defined($_) || eval { eval('1'.substr $_,0,0) } }
174			@dirlist;
175    }
176
177    foreach (@dirlist) {
178	next unless defined && -d && -w _;
179	$tmpdir = $_;
180	last;
181    }
182    $tmpdir = $self->curdir unless defined $tmpdir;
183    $tmpdir = defined $tmpdir && $self->canonpath($tmpdir);
184    if ( !$self->file_name_is_absolute($tmpdir) ) {
185        # See [perl #120593] for the full details
186        # If possible, return a full path, rather than '.' or 'lib', but
187        # jump through some hoops to avoid returning a tainted value.
188        ($tmpdir) = grep {
189            $taint     ? ! Scalar::Util::tainted($_) :
190            $] < 5.007 ? eval { eval('1'.substr $_,0,0) } : 1
191        } $self->rel2abs($tmpdir), $tmpdir;
192    }
193    return $tmpdir;
194}
195
196sub tmpdir {
197    my $cached = $_[0]->_cached_tmpdir('TMPDIR');
198    return $cached if defined $cached;
199    $_[0]->_cache_tmpdir($_[0]->_tmpdir( $ENV{TMPDIR}, "/tmp" ), 'TMPDIR');
200}
201
202=item updir
203
204Returns a string representation of the parent directory.  ".." on UNIX.
205
206=cut
207
208sub updir { '..' }
209use constant _fn_updir => "..";
210
211=item no_upwards
212
213Given a list of file names, strip out those that refer to a parent
214directory. (Does not strip symlinks, only '.', '..', and equivalents.)
215
216=cut
217
218sub no_upwards {
219    my $self = shift;
220    return grep(!/^\.{1,2}\z/s, @_);
221}
222
223=item case_tolerant
224
225Returns a true or false value indicating, respectively, that alphabetic
226is not or is significant when comparing file specifications.
227
228=cut
229
230sub case_tolerant { 0 }
231use constant _fn_case_tolerant => 0;
232
233=item file_name_is_absolute
234
235Takes as argument a path and returns true if it is an absolute path.
236
237This does not consult the local filesystem on Unix, Win32, OS/2 or Mac
238OS (Classic).  It does consult the working environment for VMS (see
239L<File::Spec::VMS/file_name_is_absolute>).
240
241=cut
242
243sub file_name_is_absolute {
244    my ($self,$file) = @_;
245    return scalar($file =~ m:^/:s);
246}
247
248=item path
249
250Takes no argument, returns the environment variable PATH as an array.
251
252=cut
253
254sub path {
255    return () unless exists $ENV{PATH};
256    my @path = split(':', $ENV{PATH});
257    foreach (@path) { $_ = '.' if $_ eq '' }
258    return @path;
259}
260
261=item join
262
263join is the same as catfile.
264
265=cut
266
267sub join {
268    my $self = shift;
269    return $self->catfile(@_);
270}
271
272=item splitpath
273
274    ($volume,$directories,$file) = File::Spec->splitpath( $path );
275    ($volume,$directories,$file) = File::Spec->splitpath( $path,
276                                                          $no_file );
277
278Splits a path into volume, directory, and filename portions. On systems
279with no concept of volume, returns '' for volume.
280
281For systems with no syntax differentiating filenames from directories,
282assumes that the last file is a path unless $no_file is true or a
283trailing separator or /. or /.. is present. On Unix this means that $no_file
284true makes this return ( '', $path, '' ).
285
286The directory portion may or may not be returned with a trailing '/'.
287
288The results can be passed to L</catpath()> to get back a path equivalent to
289(usually identical to) the original path.
290
291=cut
292
293sub splitpath {
294    my ($self,$path, $nofile) = @_;
295
296    my ($volume,$directory,$file) = ('','','');
297
298    if ( $nofile ) {
299        $directory = $path;
300    }
301    else {
302        $path =~ m|^ ( (?: .* / (?: \.\.?\z )? )? ) ([^/]*) |xs;
303        $directory = $1;
304        $file      = $2;
305    }
306
307    return ($volume,$directory,$file);
308}
309
310
311=item splitdir
312
313The opposite of L</catdir()>.
314
315    @dirs = File::Spec->splitdir( $directories );
316
317$directories must be only the directory portion of the path on systems
318that have the concept of a volume or that have path syntax that differentiates
319files from directories.
320
321Unlike just splitting the directories on the separator, empty
322directory names (C<''>) can be returned, because these are significant
323on some OSs.
324
325On Unix,
326
327    File::Spec->splitdir( "/a/b//c/" );
328
329Yields:
330
331    ( '', 'a', 'b', '', 'c', '' )
332
333=cut
334
335sub splitdir {
336    return split m|/|, $_[1], -1;  # Preserve trailing fields
337}
338
339
340=item catpath()
341
342Takes volume, directory and file portions and returns an entire path. Under
343Unix, $volume is ignored, and directory and file are concatenated.  A '/' is
344inserted if needed (though if the directory portion doesn't start with
345'/' it is not added).  On other OSs, $volume is significant.
346
347=cut
348
349sub catpath {
350    my ($self,$volume,$directory,$file) = @_;
351
352    if ( $directory ne ''                &&
353         $file ne ''                     &&
354         substr( $directory, -1 ) ne '/' &&
355         substr( $file, 0, 1 ) ne '/'
356    ) {
357        $directory .= "/$file" ;
358    }
359    else {
360        $directory .= $file ;
361    }
362
363    return $directory ;
364}
365
366=item abs2rel
367
368Takes a destination path and an optional base path returns a relative path
369from the base path to the destination path:
370
371    $rel_path = File::Spec->abs2rel( $path ) ;
372    $rel_path = File::Spec->abs2rel( $path, $base ) ;
373
374If $base is not present or '', then L<cwd()|Cwd> is used. If $base is
375relative, then it is converted to absolute form using
376L</rel2abs()>. This means that it is taken to be relative to
377L<cwd()|Cwd>.
378
379On systems that have a grammar that indicates filenames, this ignores the
380$base filename. Otherwise all path components are assumed to be
381directories.
382
383If $path is relative, it is converted to absolute form using L</rel2abs()>.
384This means that it is taken to be relative to L<cwd()|Cwd>.
385
386No checks against the filesystem are made, so the result may not be correct if
387C<$base> contains symbolic links.  (Apply
388L<Cwd::abs_path()|Cwd/abs_path> beforehand if that
389is a concern.)  On VMS, there is interaction with the working environment, as
390logicals and macros are expanded.
391
392Based on code written by Shigio Yamaguchi.
393
394=cut
395
396sub abs2rel {
397    my($self,$path,$base) = @_;
398    $base = Cwd::getcwd() unless defined $base and length $base;
399
400    ($path, $base) = map $self->canonpath($_), $path, $base;
401
402    my $path_directories;
403    my $base_directories;
404
405    if (grep $self->file_name_is_absolute($_), $path, $base) {
406	($path, $base) = map $self->rel2abs($_), $path, $base;
407
408	my ($path_volume) = $self->splitpath($path, 1);
409	my ($base_volume) = $self->splitpath($base, 1);
410
411	# Can't relativize across volumes
412	return $path unless $path_volume eq $base_volume;
413
414	$path_directories = ($self->splitpath($path, 1))[1];
415	$base_directories = ($self->splitpath($base, 1))[1];
416
417	# For UNC paths, the user might give a volume like //foo/bar that
418	# strictly speaking has no directory portion.  Treat it as if it
419	# had the root directory for that volume.
420	if (!length($base_directories) and $self->file_name_is_absolute($base)) {
421	    $base_directories = $self->rootdir;
422	}
423    }
424    else {
425	my $wd= ($self->splitpath(Cwd::getcwd(), 1))[1];
426	$path_directories = $self->catdir($wd, $path);
427	$base_directories = $self->catdir($wd, $base);
428    }
429
430    # Now, remove all leading components that are the same
431    my @pathchunks = $self->splitdir( $path_directories );
432    my @basechunks = $self->splitdir( $base_directories );
433
434    if ($base_directories eq $self->rootdir) {
435      return $self->curdir if $path_directories eq $self->rootdir;
436      shift @pathchunks;
437      return $self->canonpath( $self->catpath('', $self->catdir( @pathchunks ), '') );
438    }
439
440    my @common;
441    while (@pathchunks && @basechunks && $self->_same($pathchunks[0], $basechunks[0])) {
442        push @common, shift @pathchunks ;
443        shift @basechunks ;
444    }
445    return $self->curdir unless @pathchunks || @basechunks;
446
447    # @basechunks now contains the directories the resulting relative path
448    # must ascend out of before it can descend to $path_directory.  If there
449    # are updir components, we must descend into the corresponding directories
450    # (this only works if they are no symlinks).
451    my @reverse_base;
452    while( defined(my $dir= shift @basechunks) ) {
453	if( $dir ne $self->updir ) {
454	    unshift @reverse_base, $self->updir;
455	    push @common, $dir;
456	}
457	elsif( @common ) {
458	    if( @reverse_base && $reverse_base[0] eq $self->updir ) {
459		shift @reverse_base;
460		pop @common;
461	    }
462	    else {
463		unshift @reverse_base, pop @common;
464	    }
465	}
466    }
467    my $result_dirs = $self->catdir( @reverse_base, @pathchunks );
468    return $self->canonpath( $self->catpath('', $result_dirs, '') );
469}
470
471sub _same {
472  $_[1] eq $_[2];
473}
474
475=item rel2abs()
476
477Converts a relative path to an absolute path.
478
479    $abs_path = File::Spec->rel2abs( $path ) ;
480    $abs_path = File::Spec->rel2abs( $path, $base ) ;
481
482If $base is not present or '', then L<cwd()|Cwd> is used. If $base is
483relative, then it is converted to absolute form using
484L</rel2abs()>. This means that it is taken to be relative to
485L<cwd()|Cwd>.
486
487On systems that have a grammar that indicates filenames, this ignores
488the $base filename. Otherwise all path components are assumed to be
489directories.
490
491If $path is absolute, it is cleaned up and returned using L</canonpath()>.
492
493No checks against the filesystem are made.  On VMS, there is
494interaction with the working environment, as logicals and
495macros are expanded.
496
497Based on code written by Shigio Yamaguchi.
498
499=cut
500
501sub rel2abs {
502    my ($self,$path,$base ) = @_;
503
504    # Clean up $path
505    if ( ! $self->file_name_is_absolute( $path ) ) {
506        # Figure out the effective $base and clean it up.
507        if ( !defined( $base ) || $base eq '' ) {
508	    $base = Cwd::getcwd();
509        }
510        elsif ( ! $self->file_name_is_absolute( $base ) ) {
511            $base = $self->rel2abs( $base ) ;
512        }
513        else {
514            $base = $self->canonpath( $base ) ;
515        }
516
517        # Glom them together
518        $path = $self->catdir( $base, $path ) ;
519    }
520
521    return $self->canonpath( $path ) ;
522}
523
524=back
525
526=head1 COPYRIGHT
527
528Copyright (c) 2004 by the Perl 5 Porters.  All rights reserved.
529
530This program is free software; you can redistribute it and/or modify
531it under the same terms as Perl itself.
532
533Please submit bug reports and patches to perlbug@perl.org.
534
535=head1 SEE ALSO
536
537L<File::Spec>
538
539=cut
540
541# Internal method to reduce xx\..\yy -> yy
542sub _collapse {
543    my($fs, $path) = @_;
544
545    my $updir  = $fs->updir;
546    my $curdir = $fs->curdir;
547
548    my($vol, $dirs, $file) = $fs->splitpath($path);
549    my @dirs = $fs->splitdir($dirs);
550    pop @dirs if @dirs && $dirs[-1] eq '';
551
552    my @collapsed;
553    foreach my $dir (@dirs) {
554        if( $dir eq $updir              and   # if we have an updir
555            @collapsed                  and   # and something to collapse
556            length $collapsed[-1]       and   # and its not the rootdir
557            $collapsed[-1] ne $updir    and   # nor another updir
558            $collapsed[-1] ne $curdir         # nor the curdir
559          )
560        {                                     # then
561            pop @collapsed;                   # collapse
562        }
563        else {                                # else
564            push @collapsed, $dir;            # just hang onto it
565        }
566    }
567
568    return $fs->catpath($vol,
569                        $fs->catdir(@collapsed),
570                        $file
571                       );
572}
573
574
5751;
576