File Coverage

blib/lib/File/Find/Node.pm
Criterion Covered Total %
statement 201 215 93.4
branch 50 66 75.7
condition 27 33 81.8
subroutine 59 59 100.0
pod 37 37 100.0
total 374 410 91.2


line stmt bran cond sub pod time code
1             package File::Find::Node;
2              
3 2     2   44430 use 5.006;
  2         4  
  2         62  
4 2     2   8 use strict;
  2         4  
  2         52  
5 2     2   8 use warnings;
  2         12  
  2         58  
6 2     2   10 use Carp;
  2         4  
  2         242  
7              
8             our $VERSION = '0.03';
9              
10             #
11             # constructor
12             #
13              
14 2     2   18 use constant PATH => 0;
  2         2  
  2         128  
15 2     2   10 use constant NAME => 1;
  2         4  
  2         76  
16 2     2   10 use constant LEVEL => 2;
  2         4  
  2         84  
17 2     2   10 use constant PRUNE => 3;
  2         4  
  2         108  
18 2     2   12 use constant FOLLOW => 4;
  2         4  
  2         80  
19 2     2   12 use constant PARENT => 5;
  2         2  
  2         104  
20 2     2   12 use constant PROCESS => 6;
  2         4  
  2         98  
21 2     2   10 use constant POSTPROC => 7;
  2         4  
  2         74  
22 2     2   10 use constant FILTER => 8;
  2         4  
  2         102  
23 2     2   22 use constant ERRPROC => 9;
  2         4  
  2         98  
24 2     2   10 use constant STAT => 10;
  2         2  
  2         98  
25 2     2   10 use constant ARG => 11;
  2         4  
  2         84  
26 2     2   12 use constant USER => 12;
  2         24  
  2         82  
27 2     2   10 use constant GROUP => 13;
  2         4  
  2         98  
28 2     2   8 use constant MAXFORK => 14;
  2         4  
  2         3988  
29              
30             sub new {
31 28     28 1 88523 my ($class, $path) = @_;
32 28 50       373 defined($path) or $path = ".";
33 28         138 $path =~ s{/+}{/}g;
34 28 50       168 $path =~ s{/$}{} if $path ne "/";
35 28         438 my $self = [
36             $path, # PATH
37             $path, # NAME
38             0, # LEVEL
39             0, # PRUNE
40             0, # FOLLOW
41             undef, # PARENT
42             undef, # PROCESS
43             undef, # POSTPROC
44             undef, # FILTER
45             undef, # ERRPROC
46             undef, # STAT
47             undef, # ARG
48             {}, # USER cache for getpwuid()
49             {}, # GROUP cache for getgrgid()
50             0 # MAXFORK
51             ];
52 28         125 $self->[NAME] =~ s{.*/}{};
53 28         429 bless($self);
54             }
55              
56             #
57             # private object methods
58             #
59              
60             # _error calls error callback function or calls carp().
61              
62             sub _error {
63 1     1   7 my ($self, $what) = @_;
64 1 50       3 if ($self->[ERRPROC]) {
65 1         8 $self->[ERRPROC]->($self, $what);
66             }
67             else {
68 0         0 my $path = $self->[PATH];
69 0         0 carp(__PACKAGE__, " - $what($path) - $!");
70             }
71             }
72              
73             # _cycle returns true if this directory is in the parent chain
74              
75             sub _cycle {
76 10     10   18 my $self = shift;
77 10         1542 my ($inum, $dev) = ($self->inum, $self->dev);
78 10         50 for (my $p = $self->[PARENT]; $p; $p = $p->[PARENT]) {
79 10 100 66     22 return 1 if $dev == $p->dev && $inum == $p->inum;
80             }
81 6         224 0;
82             }
83              
84             #
85             # public object methods
86             #
87              
88             sub process {
89 26     26 1 2872 my $self = shift;
90 26         648 $self->[PROCESS] = shift;
91 26         84 $self;
92             }
93              
94             sub post_process {
95 6     6 1 92 my $self = shift;
96 6         16 $self->[POSTPROC] = shift;
97 6         20 $self;
98             }
99              
100             sub filter {
101 2     2 1 16 my $self = shift;
102 2         8 $self->[FILTER] = shift;
103 2         12 $self;
104             }
105              
106             sub error_process {
107 2     2 1 44 my $self = shift;
108 2         3 $self->[ERRPROC] = shift;
109 2         8 $self;
110             }
111              
112             sub arg {
113 34     34 1 90 my $self = shift;
114 34 100       206 $self->[ARG] or ($self->[ARG] = {});
115             }
116              
117             sub prune {
118 2     2 1 16 my $self = shift;
119 2         4 $self->[PRUNE] = 1;
120 2         8 $self;
121             }
122              
123             sub stop {
124 2     2 1 16 my $self = shift;
125 2         10 for (my $p = $self; $p; $p = $p->[PARENT]) {
126 6         18 $p->[PRUNE] = 1;
127             }
128 2         8 $self;
129             }
130              
131             sub follow {
132 4     4 1 24 my $self = shift;
133 4   33     22 $self->[FOLLOW] = (@_ == 0 || shift);
134 4         18 $self;
135             }
136              
137             sub fork {
138 2     2 1 18 my $self = shift;
139 2 50 33     130 $self->[MAXFORK] = ($self->[LEVEL] > 0 && @_ > 0) ? shift : 0;
140 2         8 $self;
141             }
142              
143             sub path {
144 238     238 1 17151 shift->[PATH];
145             }
146              
147             sub name {
148 18     18 1 80 shift->[NAME];
149             }
150              
151             sub parent {
152 56     56 1 2664 shift->[PARENT];
153             }
154              
155             sub level {
156 26     26 1 224 shift->[LEVEL];
157             }
158              
159             # These methods return saved stat info
160              
161             sub stat {
162 2     2 1 20 @{shift->[STAT]};
  2         10  
163             }
164              
165             sub dev {
166 22     22 1 144 shift->[STAT]->[0];
167             }
168              
169             sub inum {
170 24     24 1 334 shift->[STAT]->[1];
171             }
172              
173             sub ino {
174 2     2 1 12 shift->[STAT]->[1];
175             }
176              
177             sub mode {
178 4     4 1 28 shift->[STAT]->[2];
179             }
180              
181             sub perm {
182 6     6 1 106 shift->[STAT]->[2] & 07777;
183             }
184              
185             sub type {
186 228     228 1 6104 my $idx = (shift->[STAT]->[2] >> 12) & 017;
187 228         2652 ("?", "p", "c", "?", "d", "?", "b", "?",
188             "f", "?", "l", "?", "s", "?", "?", "?")[$idx];
189             }
190              
191             sub links {
192 4     4 1 28 shift->[STAT]->[3];
193             }
194              
195             sub nlink {
196 2     2 1 10 shift->[STAT]->[3];
197             }
198              
199             sub uid {
200 6     6 1 3218 shift->[STAT]->[4];
201             }
202              
203             sub gid {
204 6     6 1 2700 shift->[STAT]->[5];
205             }
206              
207             sub user {
208 2     2 1 28 my $self = shift;
209 2         8 my $uid = $self->uid;
210 2 50       14 if (exists($self->[USER]->{$uid})) {
211 0         0 return $self->[USER]->{$uid};
212             }
213 2         98 my $user = getpwuid($uid);
214 2 50       284 $self->[USER]->{$uid} = defined($user) ? $user : $uid;
215             }
216              
217             sub group {
218 2     2 1 20 my $self = shift;
219 2         10 my $gid = $self->gid;
220 2 50       250 if (exists($self->[GROUP]->{$gid})) {
221 0         0 return $self->[GROUP]->{$gid};
222             }
223 2         124 my $group = getgrgid($gid);
224 2 50       36 $self->[GROUP]->{$gid} = defined($group) ? $group : $gid;
225             }
226              
227             sub rdev {
228 2     2 1 12 shift->[STAT]->[6];
229             }
230              
231             sub size {
232 2     2 1 10 shift->[STAT]->[7];
233             }
234              
235             sub atime {
236 2     2 1 10 shift->[STAT]->[8];
237             }
238              
239             sub mtime {
240 2     2 1 12 shift->[STAT]->[9];
241             }
242              
243             sub ctime {
244 2     2 1 10 shift->[STAT]->[10];
245             }
246              
247             sub blksize {
248 2     2 1 10 shift->[STAT]->[11];
249             }
250              
251             sub blocks {
252 2     2 1 12 shift->[STAT]->[12];
253             }
254              
255             # empty returns true for an empty directory or a zero length regular file,
256             # otherwise false.
257              
258             sub empty {
259 4     4 1 22 my $self = shift;
260 4         10 my $ftype = $self->type;
261 4 50       46 if ($ftype eq "f") {
    50          
262 0         0 return $self->size == 0;
263             }
264             elsif ($ftype eq "d") {
265 4         6 my $dirh;
266 4 50       110 if (!opendir($dirh, $self->[PATH])) {
267 0         0 $self->_error("opendir");
268 0         0 return 0;
269             }
270 4         8 my $ret = 1;
271 4         52 while (my $name = readdir($dirh)) {
272 6 100 100     66 if ($name ne "." && $name ne "..") {
273 2         10 $ret = 0;
274 2         6 last;
275             }
276             }
277 4         44 closedir($dirh);
278 4         30 return $ret;
279             }
280 0         0 0;
281             }
282              
283             # refresh calls stat() or lstat() to load saved stat info
284              
285             sub refresh {
286 187     187 1 341 my $self = shift;
287 187         851 my $path = $self->[PATH];
288 187         277 my @stat;
289 187 100       1602 if ($self->[FOLLOW]) {
290 22 100       1236 @stat = CORE::stat($path) or @stat = CORE::lstat($path);
291             }
292             else {
293 165         7577 @stat = CORE::lstat($path);
294             }
295 187 100       962 if (@stat) {
296 186         832 $self->[STAT] = \@stat;
297             }
298             else {
299 1         9 $self->_error("stat");
300             }
301 187         1759 $self;
302             }
303              
304             # find performs the directory traversal
305              
306             sub find {
307 2     2   20 no warnings "recursion";
  2         4  
  2         1708  
308 185     185 1 512 my $self = shift;
309 185 100       416 $self->refresh->[STAT] or return 0; # loads stat info
310              
311             # avoid cycles
312              
313 184         698 my $ftype = $self->type;
314 184 100 100     1495 return 0 if $ftype eq "d" && $self->[FOLLOW] && $self->_cycle;
      100        
315              
316             # call process callback
317              
318 180 100       916 if ($self->[PROCESS]) {
319 176         1873 $self->[PROCESS]->($self);
320             }
321              
322             # skip directory if pruned
323              
324 180 100 100     81651 return 0 if $ftype ne "d" || $self->[PRUNE];
325              
326             # fork sub process if requested by $f->fork
327              
328 54         86 my $forked = 0;
329 54 100 100     459 if ($self->[LEVEL] > 0 && $self->[MAXFORK] > 1) {
330 2         2811 my $pid = CORE::fork;
331 2 50       270 if (!defined($pid)) {
    100          
332 0         0 $self->_error("fork");
333             }
334             elsif ($pid == 0) { # sub process continues
335 1         11 $forked = 1;
336             }
337             else {
338 1         140 return $self->[MAXFORK] # parent process returns
339             }
340             }
341              
342             # read and filter the directory entries
343              
344 53         1339 my $path = $self->[PATH];
345 53         87 my $dirh;
346 53 50       3570 if (!opendir($dirh, $path)) {
347 0         0 $self->_error("opendir");
348 0 0       0 exit(0) if $forked;
349 0         0 return 0;
350             }
351 53 100       1724 my @dirent = $self->[FILTER] ?
352             $self->[FILTER]->(readdir($dirh)) : readdir($dirh);
353 53         1018 closedir($dirh);
354              
355             # visit the directory entries
356              
357 53         119 my $maxfork = my $numfork = 0;
358 53         126 foreach my $name (@dirent) {
359 255 100 100     4378 next if $name eq "." || $name eq "..";
360              
361             # build child object
362              
363 157         393 my $child;
364 157         981 @$child = @$self;
365 157 50       1480 $child->[PATH] = $path ne "/" ? "$path/$name" : "/$name";
366 157         599 $child->[NAME] = $name;
367 157         301 $child->[PARENT] = $self;
368 157         1284 $child->[LEVEL]++;
369 157         1131 $child->[STAT] = undef;
370 157         337 $child->[ARG] = undef;
371 157         217 $child->[MAXFORK] = 0;
372 157         555 bless($child);
373              
374             # wait for sub processes to exit
375              
376 157   66     834 while ($numfork > 0 && $numfork >= $maxfork) {
377 0         0 wait;
378 0         0 $numfork--;
379             }
380              
381             # visit the child with a recursive call
382              
383 157         492 my $forkinfo = $child->find;
384              
385 156 100       558 if ($forkinfo > 1) { # a sub process was forked
386 1         20 $numfork++;
387 1         3 $maxfork = $forkinfo;
388             }
389 156 100       2344 last if $self->[PRUNE]; # may have been pruned by child
390             }
391              
392             # call post_process callback
393              
394 52 100 100     465 if (!$self->[PRUNE] && $self->[POSTPROC]) {
395 15         54 $self->[POSTPROC]->($self);
396             }
397              
398             # wait for any remaining sub processes
399              
400 52         578593 while ($numfork-- > 0) {
401 1         33 wait;
402             }
403 52 100       728 exit(0) if $forked;
404 51         686 return 0;
405             }
406             1;
407              
408             __END__