File Coverage

blib/lib/Filesys/POSIX/Real/Directory.pm
Criterion Covered Total %
statement 91 119 76.4
branch 25 50 50.0
condition 6 9 66.6
subroutine 18 21 85.7
pod 12 14 85.7
total 152 213 71.3


line stmt bran cond sub pod time code
1             # Copyright (c) 2016, cPanel, Inc.
2             # All rights reserved.
3             # http://cpanel.net/
4             #
5             # This is free software; you can redistribute it and/or modify it under the same
6             # terms as Perl itself. See the LICENSE file for further details.
7              
8             package Filesys::POSIX::Real::Directory;
9              
10 13     13   390 use strict;
  13         13  
  13         278  
11 13     13   32 use warnings;
  13         12  
  13         237  
12              
13 13     13   37 use Filesys::POSIX::Bits;
  13         13  
  13         2864  
14 13     13   51 use Filesys::POSIX::Directory ();
  13         9  
  13         202  
15              
16 13     13   36 use Errno qw(ENOENT);
  13         8  
  13         13562  
17              
18             our @ISA = qw(Filesys::POSIX::Directory);
19              
20             sub new {
21 25     25 0 1044 my ( $class, $path, $inode ) = @_;
22              
23             return bless {
24             'path' => $path,
25             'inode' => $inode,
26             'mtime' => 0,
27             'overlays' => {},
28             'detached' => {},
29             'skipped' => {},
30             'members' => {
31             '.' => $inode,
32 25 100       242 '..' => $inode->{'parent'} ? $inode->{'parent'} : $inode
33             }
34             }, $class;
35             }
36              
37             sub _sync_all {
38 2     2   3 my ($self) = @_;
39 2 50       33 my $mtime = ( lstat $self->{'path'} )[9] or Carp::confess("$!");
40              
41 2 50       11 return unless $mtime > $self->{'mtime'};
42              
43 2         7 $self->open;
44              
45             # This uses readdir to bypass the aliased/detached overlays
46 2         36 while ( my $item = readdir $self->{'dh'} ) {
47 9         15 $self->_sync_member($item);
48             }
49              
50 2         7 $self->close;
51              
52 2         3 $self->{'mtime'} = $mtime;
53             }
54              
55             sub _sync_member {
56 40     40   43 my ( $self, $name ) = @_;
57 40         73 my $subpath = "$self->{'path'}/$name";
58 40         634 my @st = lstat $subpath;
59              
60 40 50 66     240 if ( scalar @st == 0 && $!{'ENOENT'} ) {
61 15         199 delete $self->{'members'}->{$name};
62 15         21 return;
63             }
64              
65 25 50       43 Carp::confess("$!") unless @st;
66              
67 25 100       45 if ( exists $self->{'members'}->{$name} ) {
68 7         18 $self->{'members'}->{$name}->update(@st);
69             }
70             else {
71             $self->{'members'}->{$name} = Filesys::POSIX::Real::Inode->from_disk(
72             $subpath,
73             'st_info' => \@st,
74             'dev' => $self->{'inode'}->{'dev'},
75 18         88 'parent' => $self->{'inode'}
76             );
77             }
78             }
79              
80             sub get {
81 56     56 1 52 my ( $self, $name ) = @_;
82 56 100       124 return undef if ( exists $self->{'detached'}->{$name} );
83              
84 54 100       100 return $self->{'overlays'}->{$name} if ( exists $self->{'overlays'}->{$name} );
85              
86 40 100       99 $self->_sync_member($name) unless exists $self->{'members'}->{$name};
87 40         118 return $self->{'members'}->{$name};
88             }
89              
90             sub set {
91 9     9 1 11 my ( $self, $name, $inode ) = @_;
92 9         9 delete $self->{'detached'}->{$name};
93 9         14 $self->{'overlays'}->{$name} = $inode;
94 9         31 return $inode;
95             }
96              
97             sub rename_member {
98 3     3 1 11 my ( $self, undef, $olddir, $oldname, $newname ) = @_;
99              
100             # TODO: This operation has no awareness of aliasing and detaching
101             return rename( $olddir->path . '/' . $oldname, $self->path . '/' . $newname )
102 3   66     9 && do {
103             $olddir->_sync_member($oldname);
104             $self->_sync_member($newname);
105             1;
106             };
107             }
108              
109             sub exists {
110 10     10 1 14 my ( $self, $name ) = @_;
111 10 50       31 return '' if exists $self->{'detached'}->{$name};
112 10 50       24 return 1 if exists $self->{'overlays'}->{$name};
113              
114 10         21 $self->_sync_member($name);
115 10         36 return exists $self->{'members'}->{$name};
116             }
117              
118             sub delete {
119 0     0 1 0 my ( $self, $name ) = @_;
120              
121 0 0       0 if ( exists $self->{'detached'}->{$name} ) {
122 0         0 return delete $self->{'detached'}->{$name};
123             }
124              
125 0 0       0 if ( exists $self->{'overlays'}->{$name} ) {
126 0         0 return delete $self->{'overlays'}->{$name};
127             }
128              
129 0 0       0 my $member = $self->{'members'}->{$name} or return;
130 0         0 my $subpath = "$self->{'path'}/$name";
131              
132 0 0       0 if ( $member->dir ) {
133 0         0 rmdir($subpath);
134             }
135             else {
136 0         0 unlink($subpath);
137             }
138              
139 0 0       0 if ($!) {
140 0 0       0 Carp::confess("$!") unless $!{'ENOENT'};
141             }
142              
143 0         0 my $now = time;
144 0         0 @{ $self->{'inode'} }{qw(mtime ctime)} = ( $now, $now );
  0         0  
145              
146 0         0 my $inode = $self->{'members'}->{$name};
147 0         0 delete $self->{'members'}->{$name};
148              
149 0         0 return $inode;
150             }
151              
152             sub detach {
153 1     1 1 2 my ( $self, $name ) = @_;
154              
155 1 50       4 return undef if ( exists $self->{'detached'}->{$name} );
156              
157 1         2 $self->{'detached'}->{$name} = undef;
158              
159 1         3 foreach my $table (qw(members overlays)) {
160 1 50       4 next unless exists $self->{$table}->{$name};
161              
162 1         5 return $self->{$table}->{$name};
163             }
164             }
165              
166             sub list {
167 2     2 1 8 my ( $self, $name ) = @_;
168 2         7 $self->_sync_all;
169              
170 2         3 my %union = ( %{ $self->{'members'} }, %{ $self->{'overlays'} } );
  2         6  
  2         9  
171              
172 2         3 delete @union{ keys %{ $self->{'detached'} } };
  2         6  
173              
174 2         8 return keys %union;
175             }
176              
177             sub count {
178 0     0 1 0 scalar( shift->list );
179             }
180              
181             sub open {
182 7     7 1 21 my ($self) = @_;
183              
184 7         18 @{ $self->{'skipped'} }{ keys %{ $self->{'overlays'} } } =
  7         20  
185 7         11 values %{ $self->{'overlays'} };
  7         53  
186              
187 7         32 $self->close;
188              
189 7 50       240 opendir( $self->{'dh'}, $self->{'path'} ) or Carp::confess("$!");
190              
191 7         47 return $self;
192             }
193              
194             sub rewind {
195 0     0 1 0 my ($self) = @_;
196              
197 0         0 @{ $self->{'skipped'} }{ keys %{ $self->{'overlays'} } } =
  0         0  
198 0         0 values %{ $self->{'overlays'} };
  0         0  
199              
200 0 0       0 if ( $self->{'dh'} ) {
201 0         0 rewinddir $self->{'dh'};
202             }
203              
204 0         0 return;
205             }
206              
207             sub read {
208 34     34 1 51 my ($self) = @_;
209 34         22 my $item;
210              
211             do {
212 34 50       55 if ( $self->{'dh'} ) {
213 34         148 $item = readdir $self->{'dh'};
214             }
215              
216 34 100       45 if ( defined $item ) {
217 29         86 delete $self->{'skipped'}->{$item};
218             }
219             else {
220 5         7 $item = each %{ $self->{'skipped'} };
  5         22  
221             }
222 34   66     25 } while ( $item && $self->{'detached'}->{$item} );
223              
224 34 50       50 if (wantarray) {
225 0         0 return ( $item, $self->get($item) );
226             }
227              
228 34         63 return $item;
229             }
230              
231             sub close {
232 13     13 1 13 my ($self) = @_;
233              
234 13 100       39 if ( $self->{'dh'} ) {
235 6         69 closedir $self->{'dh'};
236 6         19 delete $self->{'dh'};
237             }
238              
239 13         32 return;
240             }
241              
242             sub path {
243 6     6 0 11 my ($self) = @_;
244 6         84 return $self->{'path'};
245             }
246              
247             1;