package Linux::Perl::getdents;
=encoding utf-8
=head1 NAME
Linux:Perl::getdents - read full directory information
=head1 SYNOPSIS
#Platform-specific invocation uses e.g.:
# Linux::Perl::getdents::arm->getdents(...)
my @entities = Linux::Perl::getdents->getdents( $filehandle, $buffer_size );
=head1 DESCRIPTION
This module provides support for the kernel-level logic to read directories.
Directories store more than just the node names that Perl’s C<readdir()>
returns; for example, they store the file type and node number. By calling
the kernel’s C<getdents> logic directly, you can get this
information without making additional system calls.
=cut
use strict;
use warnings;
use parent qw(
Linux::Perl::Base
Linux::Perl::Base::BitsTest
);
use Linux::Perl;
use Linux::Perl::Endian;
use Linux::Perl::EasyPack;
use constant {
DT_UNKNOWN => 0,
DT_FIFO => 1,
DT_CHR => 2,
DT_DIR => 4,
DT_BLK => 6,
DT_REG => 8,
DT_LNK => 10,
DT_SOCK => 12,
DT_WHT => 14,
};
my ($lde64_keys, $lde64_pack, $lde64_start_size);
BEGIN {
($lde64_keys, $lde64_pack) = Linux::Perl::EasyPack::split_pack_list(
ino => __PACKAGE__->_PACK_u64(), #ino64_t
off => __PACKAGE__->_PACK_u64(), #off64_t
reclen => 'S!',
type => 'C',
#name => 'a*',
);
$lde64_start_size = length pack $lde64_pack;
}
=head1 METHODS
In addition to the following, this module exposes the constants
C<DT_UNKNOWN()> et al. (cf. C<man 2 getdents>)
=head2 @ENTRIES = I<CLASS>->getdents( $FILEHANDLE_OR_FD, $READ_SIZE )
Reads from the given $FILEHANDLE_OR_FD using a buffer of $READ_SIZE bytes.
There’s no good way to know how many @ENTRIES you can receive given the
$READ_SIZE, unfortunately.
The return is a list of hash references; each hash contains the keys
C<ino>, C<off>, C<type>, and C<name>. These correspond with the relevant
parts of struct C<linux_dirent64> (cf. C<man 2 getdents>).
(In scalar context, this returns the number of hash references that would
be returned in list context.)
For now, this is implemented via the C<getdents64> system call.
B<NOTE:> Perl 5.20 and earlier doesn’t understand C<fileno()> on a directory
handle, so to use this function you’ll need to pass the file descriptor rather
than the handle. (To get the file descriptor, you can parse F</proc/$$/fd>
for the symlink that refers to the directory’s path. See this module’s tests
for an implementation of this.)
=cut
sub getdents {
my ($class, $fh_or_fileno, $bufsize) = @_;
$class = $class->_get_arch_module();
my $buf = "\0" x $bufsize;
my $fileno;
if ( ref $fh_or_fileno ) {
$fileno = fileno($fh_or_fileno);
if (!defined $fileno) {
die "Filehandle ($fh_or_fileno) has no underlying file descriptor!";
}
}
else {
$fileno = $fh_or_fileno;
if (!defined $fileno) {
die "Neither a filehandle nor a file descriptor was given!";
}
}
my $bytes = Linux::Perl::call(
0 + $class->NR_getdents64(),
0 + $fileno,
$buf,
0 + $bufsize,
);
my @structs;
while ($bytes > 0) {
my %struct;
@struct{ @$lde64_keys } = unpack $lde64_pack, substr( $buf, 0, $lde64_start_size, q<> );
( $struct{'name'} = substr( $buf, 0, $struct{'reclen'} - $lde64_start_size, q<> ) ) =~ tr<\0><>d;
push @structs, \%struct;
$bytes -= delete $struct{'reclen'};
}
return @structs;
}
1;