File Coverage

blib/lib/Filesys/POSIX/VFS.pm
Criterion Covered Total %
statement 80 81 98.7
branch 22 22 100.0
condition 1 2 50.0
subroutine 12 12 100.0
pod 0 6 0.0
total 115 123 93.5


line stmt bran cond sub pod time code
1             # Copyright (c) 2014, 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::VFS;
9              
10 25     25   85 use strict;
  25         25  
  25         567  
11 25     25   76 use warnings;
  25         25  
  25         458  
12              
13 25     25   79 use Filesys::POSIX::Bits;
  25         20  
  25         6256  
14 25     25   107 use Filesys::POSIX::Path ();
  25         27  
  25         294  
15 25     25   8776 use Filesys::POSIX::VFS::Inode ();
  25         35  
  25         386  
16              
17 25     25   84 use Filesys::POSIX::Error qw(throw);
  25         24  
  25         14939  
18              
19             sub new {
20 40     40 0 294 return bless {
21             'mounts' => [],
22             'devices' => {},
23             'vnodes' => {}
24             },
25             shift;
26             }
27              
28             sub statfs {
29 17     17 0 89 my ( $self, $start, %opts ) = @_;
30 17         12 my $inode = $start;
31 17         11 my $ret;
32              
33 17         32 while ( $inode->{'vnode'} ) {
34 0         0 $inode = $inode->{'vnode'};
35             }
36              
37 17 100       26 if ( $opts{'exact'} ) {
38 11         18 $ret = $self->{'vnodes'}->{$inode};
39             }
40             else {
41 6         12 $ret = $self->{'devices'}->{ $inode->{'dev'} };
42             }
43              
44 17 100       22 unless ($ret) {
45 4 100       12 throw &Errno::ENXIO unless $opts{'silent'};
46             }
47              
48 16         40 return $ret;
49             }
50              
51             sub mountlist {
52 1     1 0 1 my ($self) = @_;
53 1         1 return @{ $self->{'mounts'} };
  1         3  
54             }
55              
56             #
57             # It should be noted that any usage of pathnames in this module are entirely
58             # symbolic and are not used for canonical purposes. The higher-level
59             # filesystem layer should take on the responsibility of providing both the
60             # canonically-correct absolute pathnames for mount points, and helping locate
61             # the appropriate VFS mount point for querying purposes.
62             #
63             sub mount {
64 52     52 0 116 my ( $self, $dev, $path, $mountpoint, %data ) = @_;
65              
66 52 100       55 if ( grep { $_->{'dev'} eq $dev } @{ $self->{'mounts'} } ) {
  20         55  
  52         200  
67 2         7 throw &Errno::EBUSY;
68             }
69              
70 50   50     258 $data{'special'} ||= scalar $dev;
71              
72             #
73             # Generate a generic BSD-style filesystem type string.
74             #
75 50         122 my $type = lc ref $dev;
76 50         331 $type =~ s/^([a-z_][a-z0-9_]*::)*//;
77              
78             #
79             # Create a vnode record munged from the mountpoint and new
80             # filesystem root.
81             #
82 50         267 my $vnode = Filesys::POSIX::VFS::Inode->new( $mountpoint, $dev->{'root'} );
83              
84             #
85             # Associate the mountpoint and filesystem roots with this vnode.
86             #
87 50         106 $mountpoint->{'vnode'} = $vnode;
88 50         77 $dev->{'root'}->{'vnode'} = $vnode;
89              
90             #
91             # Generate the mount record.
92             #
93             my $mount = {
94             'mountpoint' => $mountpoint,
95             'root' => $dev->{'root'},
96             'special' => $data{'special'},
97             'dev' => $dev,
98             'type' => $type,
99             'path' => $path,
100             'vnode' => $vnode,
101              
102 50         157 'flags' => { map { $_ => $data{$_} } grep { $_ ne 'special' } keys %data }
  44         172  
  94         239  
103             };
104              
105             #
106             # Store the mount record in the ordered mount list.
107             #
108 50         68 push @{ $self->{'mounts'} }, $mount;
  50         105  
109              
110             #
111             # Associate the vnode with the mount rcord.
112             #
113 50         125 $self->{'vnodes'}->{$vnode} = $mount;
114              
115             #
116             # Finally, associate the filesystem with the mount record.
117             #
118 50         99 $self->{'devices'}->{$dev} = $mount;
119              
120 50         149 return $self;
121             }
122              
123             sub vnode {
124 1854     1854 0 1373 my ( $self, $start ) = @_;
125 1854         1219 my $inode = $start;
126              
127 1854 100       2351 return undef unless $inode;
128              
129 1760         2468 while ( $inode->{'vnode'} ) {
130 217         344 $inode = $inode->{'vnode'};
131             }
132              
133 1760         2543 my $mount = $self->{'devices'}->{ $inode->{'dev'} };
134              
135 1760 100       2185 if ( $mount->{'flags'}->{'noexec'} ) {
136 9         7 $inode->{'mode'} &= ~$S_IX;
137             }
138              
139 1760 100       2069 if ( $mount->{'flags'}->{'nosuid'} ) {
140 9         4 $inode->{'mode'} &= ~$S_ISUID;
141             }
142              
143 1760         1726 foreach (qw(uid gid)) {
144 3520 100       4693 if ( defined $mount->{'flags'}->{$_} ) {
145 66         68 $inode->{$_} = $mount->{'flags'}->{$_};
146             }
147             }
148              
149 1760         2968 return $inode;
150             }
151              
152             sub unmount {
153 4     4 0 2 my ( $self, $mount ) = @_;
154              
155             #
156             # First, check to see that the filesystem mount record found is a
157             # dependency for another mounted filesystem.
158             #
159 4         5 foreach ( @{ $self->{'mounts'} } ) {
  4         6  
160 12 100       20 next if $_ == $mount;
161              
162 8 100       16 throw &Errno::EBUSY if $_->{'mountpoint'}->{'dev'} == $mount->{'dev'};
163             }
164              
165             #
166             # Pluck the filesystem from the mount list.
167             #
168 3         6 for ( my $i = 0; $self->{'mounts'}->[$i]; $i++ ) {
169 9 100       20 next unless $self->{'mounts'}->[$i] eq $mount;
170 3         4 splice @{ $self->{'mounts'} }, $i;
  3         4  
171 3         4 last;
172             }
173              
174             #
175             # Untie the vnode reference from its original mount point and root.
176             #
177 3         3 delete $mount->{'mountpoint'}->{'vnode'};
178 3         3 delete $mount->{'root'}->{'vnode'};
179              
180             #
181             # Break references to the mount record from the per-vnode hash.
182             #
183 3         5 delete $self->{'vnodes'}->{ $mount->{'vnode'} };
184              
185             #
186             # Kill references to the mount record from the per-device hash.
187             #
188 3         4 delete $self->{'devices'}->{ $mount->{'dev'} };
189              
190 3         20 return $self;
191             }
192              
193             1;