File Coverage

blib/lib/Dir/TempChdir.pm
Criterion Covered Total %
statement 86 105 81.9
branch 11 28 39.2
condition 3 12 25.0
subroutine 20 24 83.3
pod 7 7 100.0
total 127 176 72.1


line stmt bran cond sub pod time code
1             package Dir::TempChdir;
2              
3 2     2   268153 use strict;
  2         4  
  2         74  
4 2     2   9 use warnings;
  2         4  
  2         106  
5              
6 2     2   11 use Carp ();
  2         3  
  2         23  
7 2     2   7 use Config ();
  2         6  
  2         41  
8 2     2   9 use Cwd ();
  2         2  
  2         31  
9 2     2   8 use File::Spec ();
  2         7  
  2         31  
10 2     2   8 use Scalar::Util ();
  2         3  
  2         30  
11 2     2   21 use XSLoader;
  2         4  
  2         304  
12              
13             BEGIN {
14 2     2   13 require Exporter;
15 2         30 our @ISA = qw(Exporter);
16 2         5 our @EXPORT = ();
17 2         4 our @EXPORT_OK = ();
18 2         4 our @EXPORT_TAGS = ();
19              
20 2         5 our $VERSION = '0.05';
21 2         4 our $XS_VERSION = $VERSION;
22 2         131 $VERSION = eval $VERSION; # so "use Module 0.002" won't warn on underscore
23              
24 2         1172 XSLoader::load(__PACKAGE__, $XS_VERSION);
25             }
26              
27             use constant {
28             _CURDIR => File::Spec->curdir(),
29             _HAVE_FCHDIR => $Config::Config{d_fchdir},
30 2 50       538 _O_SRCH => (
    50          
31             defined &O_SEARCH ? &O_SEARCH : # POSIX
32             defined &O_PATH ? &O_PATH : # Linux
33             undef
34             ),
35 2     2   15 };
  2         4  
36              
37             use overload
38 0     0   0 bool => sub { defined $_[0] },
39 13     13   3312 '""' => sub { Cwd::getcwd() },
40 0     0   0 '0+' => sub { Scalar::Util::refaddr($_[0]) },
41 2         21 fallback => 1
42 2     2   534 ;
  2         1283  
43              
44              
45             my $_FINAL_ERRNO;
46             my $_FINAL_ERROR;
47             my $O_PATH;
48              
49             sub new {
50 1     1 1 67591 my $class = shift;
51 1         2 my $dir = shift;
52              
53 1         5 my $self = bless {
54             _stack => [],
55             _last_errno => undef,
56             _last_error => undef,
57             _initialized => 0,
58             }, $class;
59              
60 1 50 33     5 if (defined $dir && !defined $self->pushd($dir)) {
61 0         0 return undef;
62             }
63              
64 1         28 $self->{_initialized}++;
65 1         2 return $self;
66             }
67              
68             sub _clear_errors {
69 18     18   35 my $self = shift;
70 18         35 undef $self->{_last_errno};
71 18         33 undef $self->{_last_error};
72             }
73              
74             sub pushd {
75 12     12 1 29 my $self = shift;
76 12         20 my $dir = shift;
77              
78 12         16 my $fn;
79             # Perl < 5.22 had no newfangled nonsense like the fileno of a handle
80             # returned by opendir().
81 12 50 33     40 my $dirname = ref($dir) && defined($fn = fileno $dir) ? "fileno:$fn" : $dir;
82              
83 12         121 my $cwd = Cwd::getcwd();
84 12 50       30 $cwd = sprintf('[%d:%s]', $!, $!) unless defined $cwd;
85              
86 12         15 my $dh;
87 12 50 33     483 if (_HAVE_FCHDIR && !(defined _O_SRCH and sysopen $dh, _CURDIR, _O_SRCH or opendir $dh, _CURDIR)) {
    50          
88 0         0 $self->{_last_error} = "pushd($dirname): opening '.' failed: $! [cwd:$cwd]";
89             }
90             elsif (! chdir $dir) {
91 0         0 $self->{_last_error} = "pushd($dirname): chdir() failed: $! [cwd:$cwd]";
92             }
93             else {
94 12         52 $self->_clear_errors();
95 12         20 push @{$self->{_stack}}, { dh => $dh, path => $cwd };
  12         46  
96 12         38 return $self;
97             }
98              
99 0         0 $self->{_last_errno} = $!;
100 0         0 return undef;
101             }
102              
103             sub popd {
104 6     6 1 14 my $self = shift;
105              
106 6         11 my $d = pop @{$self->{_stack}};
  6         15  
107 6 50       18 if (defined $d) {
    0          
108 6 50       66 if (chdir(_HAVE_FCHDIR ? $d->{dh} : $d->{path})) {
109 6         19 $self->_clear_errors();
110 6         120 return $self;
111             }
112 0         0 $self->{_last_errno} = $!;
113             $self->{_last_error} =
114 0         0 "popd() couldn't chdir() to (assumedly) $d->{path}: $!";
115             }
116             elsif ($self->{_initialized}) {
117 0         0 undef $!;
118 0         0 $self->_clear_errors();
119             }
120              
121 0         0 return undef;
122             }
123              
124             sub backout {
125 2     2 1 6 my $self = shift;
126              
127 2         4 splice @{$self->{_stack}}, 1;
  2         39  
128 2         15 return $self->popd();
129             }
130              
131             sub stack_size {
132 8     8 1 14 my $self = shift;
133 8         12 return scalar @{$self->{_stack}};
  8         70  
134             }
135              
136             sub errno {
137 0     0 1 0 my $self = shift;
138 0 0       0 return Scalar::Util::blessed($self) ? $self->{_last_errno} : $_FINAL_ERRNO;
139             }
140              
141             sub error {
142 0     0 1 0 my $self = shift;
143 0 0       0 return Scalar::Util::blessed($self) ? $self->{_last_error} : $_FINAL_ERROR;
144             }
145              
146             sub DESTROY {
147 1     1   4 my $self = shift;
148              
149 1         11 local($., $@, $!, $^E, $?); # recommended by perldoc perlobj
150 1 50       5 if (defined $self->backout()) {
151 1         2 undef $_FINAL_ERRNO;
152 1         9 undef $_FINAL_ERROR;
153             }
154             else {
155 0         0 $_FINAL_ERRNO = $self->{_last_errno};
156 0         0 $_FINAL_ERROR = $self->{_last_error};
157             }
158             }
159              
160             sub import {
161 2     2   33 my $this = shift;
162              
163 2   0     4 my @args = grep !$_ || $_ ne '-IGNORE_UNSAFE_CHDIR_SECURITY_RISK', @_;
164 2         5 my $HAVE_IGNORE_UNSAFE_CHDIR_SECURITY_RISK = @args < @_;
165              
166 2         4 if (_HAVE_FCHDIR) {
167 2 50       10 if ($HAVE_IGNORE_UNSAFE_CHDIR_SECURITY_RISK) {
168 0         0 Carp::carp(
169             'Useless -IGNORE_UNSAFE_CHDIR_SECURITY_RISK on fchdir() capable system'
170             );
171             }
172             }
173             elsif (! $HAVE_IGNORE_UNSAFE_CHDIR_SECURITY_RISK) {
174             Carp::croak('This system lacks support for fchdir()');
175             }
176              
177 2         115189 __PACKAGE__->export_to_level(1, $this, @args);
178             }
179              
180             1;