From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

# Filesys::POSIX Copyright (c) 2011 cPanel, Inc. All rights reserved.
# copyright@cpanel.net http://cpanel.net/
#
# Written by Erin Schönhals <erin@cpanel.net>. Released under the terms of the
# Perl Artistic License.
use strict;
use Carp qw/confess/;
sub new {
return bless {
'mounts' => [],
'devices' => {},
'vnodes' => {}
},
shift;
}
sub statfs {
my ( $self, $start, %opts ) = @_;
my $inode = $start;
my $ret;
while ( $inode->{'vnode'} ) {
$inode = $inode->{'vnode'};
}
if ( $opts{'exact'} ) {
$ret = $self->{'vnodes'}->{$inode};
}
else {
$ret = $self->{'devices'}->{ $inode->{'dev'} };
}
unless ($ret) {
confess('Not mounted') unless $opts{'silent'};
}
return $ret;
}
sub mountlist {
my ($self) = @_;
return @{ $self->{'mounts'} };
}
#
# It should be noted that any usage of pathnames in this module are entirely
# symbolic and are not used for canonical purposes. The higher-level
# filesystem layer should take on the responsibility of providing both the
# canonically-correct absolute pathnames for mount points, and helping locate
# the appropriate VFS mount point for querying purposes.
#
sub mount {
my ( $self, $dev, $path, $mountpoint, %data ) = @_;
if ( grep { $_->{'dev'} eq $dev } @{ $self->{'mounts'} } ) {
confess('Already mounted');
}
$data{'special'} ||= scalar $dev;
#
# Generate a generic BSD-style filesystem type string.
#
my $type = lc ref $dev;
$type =~ s/^([a-z_][a-z0-9_]*::)*//;
#
# Create a vnode record munged from the mountpoint and new
# filesystem root.
#
my $vnode = Filesys::POSIX::VFS::Inode->new( $mountpoint, $dev->{'root'} );
#
# Associate the mountpoint and filesystem roots with this vnode.
#
$mountpoint->{'vnode'} = $vnode;
$dev->{'root'}->{'vnode'} = $vnode;
#
# Generate the mount record.
#
my $mount = {
'mountpoint' => $mountpoint,
'root' => $dev->{'root'},
'special' => $data{'special'},
'dev' => $dev,
'type' => $type,
'path' => $path,
'vnode' => $vnode,
'flags' => { map { $_ => $data{$_} } grep { $_ ne 'special' } keys %data }
};
#
# Store the mount record in the ordered mount list.
#
push @{ $self->{'mounts'} }, $mount;
#
# Associate the vnode with the mount rcord.
#
$self->{'vnodes'}->{$vnode} = $mount;
#
# Finally, associate the filesystem with the mount record.
#
$self->{'devices'}->{$dev} = $mount;
return $self;
}
sub vnode {
my ( $self, $start ) = @_;
my $inode = $start;
return undef unless $inode;
while ( $inode->{'vnode'} ) {
$inode = $inode->{'vnode'};
}
my $mount = $self->{'devices'}->{ $inode->{'dev'} };
if ( $mount->{'flags'}->{'noexec'} ) {
$inode->{'mode'} &= ~$S_IX;
}
if ( $mount->{'flags'}->{'nosuid'} ) {
$inode->{'mode'} &= ~$S_ISUID;
}
foreach (qw/uid gid/) {
if ( defined $mount->{'flags'}->{$_} ) {
$inode->{$_} = $mount->{'flags'}->{$_};
}
}
return $inode;
}
sub unmount {
my ( $self, $mount ) = @_;
#
# First, check to see that the filesystem mount record found is a
# dependency for another mounted filesystem.
#
foreach ( @{ $self->{'mounts'} } ) {
next if $_ == $mount;
confess('Device or resource busy') if $_->{'mountpoint'}->{'dev'} == $mount->{'dev'};
}
#
# Pluck the filesystem from the mount list.
#
for ( my $i = 0; $self->{'mounts'}->[$i]; $i++ ) {
next unless $self->{'mounts'}->[$i] eq $mount;
splice @{ $self->{'mounts'} }, $i;
last;
}
#
# Untie the vnode reference from its original mount point and root.
#
delete $mount->{'mountpoint'}->{'vnode'};
delete $mount->{'root'}->{'vnode'};
#
# Break references to the mount record from the per-vnode hash.
#
delete $self->{'vnodes'}->{ $mount->{'vnode'} };
#
# Kill references to the mount record from the per-device hash.
#
delete $self->{'devices'}->{ $mount->{'dev'} };
return $self;
}
1;