File Coverage

blib/lib/File/Locate/Iterator/FileMap.pm
Criterion Covered Total %
statement 51 54 94.4
branch 4 6 66.6
condition 3 5 60.0
subroutine 14 14 100.0
pod 0 3 0.0
total 72 82 87.8


line stmt bran cond sub pod time code
1             # Copyright 2009, 2010, 2011, 2014, 2018, 2019 Kevin Ryde.
2             #
3             # This file is part of File-Locate-Iterator.
4             #
5             # File-Locate-Iterator is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option)
8             # any later version.
9             #
10             # File-Locate-Iterator is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with File-Locate-Iterator; see the file COPYING. Failing that, go to
17             # .
18              
19             package File::Locate::Iterator::FileMap;
20 4     4   8752 use 5.006;
  4         12  
21 4     4   23 use strict;
  4         8  
  4         112  
22 4     4   23 use warnings;
  4         8  
  4         117  
23 4     4   21 use Carp;
  4         8  
  4         2201  
24              
25             # uncomment this to run the ### lines
26             #use Devel::Comments;
27              
28             our $VERSION = 26;
29              
30             our %cache;
31              
32             sub _key {
33 43     43   79 my ($fh) = @_;
34 43         434 my ($dev, $ino, undef, undef, undef, undef, undef, $size) = stat ($fh);
35 43         296 return "$dev,$ino,$size,".tell($fh);
36             }
37             sub find {
38 9     9 0 21 my ($class, $fh) = @_;
39 9         25 return $cache{_key($fh)};
40             }
41              
42             # return a FileMap object which is $fh mmapped
43             sub get {
44 34     34 0 96 my ($class, $fh) = @_;
45              
46 34         67 my $key = _key($fh);
47             ### cache get: "$fh, $key, size=".(-s $fh)
48 34   66     157 return ($cache{$key} || do {
49             require File::Map;
50             # File::Map->VERSION('0.35'); # for binary handled properly, maybe
51             File::Map->VERSION('0.38'); # for tainting
52             require PerlIO::Layers;
53             require Scalar::Util;
54              
55             PerlIO::Layers::query_handle ($fh, 'mappable')
56             or croak "Handle not mappable";
57              
58             my $self = bless { key => $key,
59             mmap => undef,
60             }, $class;
61              
62             my $tell = tell($fh);
63             if ($tell < 0) {
64             # assume if tell() doesn't work then $fh is not mmappable, or in any
65             # case don't know where the current position is to map
66             croak "Cannot tell() file position: $!";
67             }
68              
69             # File::Map 0.38 does tainting itself
70             #
71             # # induce taint on the mmap -- seems to cause segvs though
72             # read $fh, $self->{'mmap'}, 0;
73             # use Devel::Peek;
74             # Dump ($self->{'mmap'});
75             #
76             # # crib: must taint before mapping, doesn't work afterwards
77             # require Taint::Util;
78             # Taint::Util::taint($self->{'mmap'});
79              
80             File::Map::map_handle ($self->{'mmap'}, $fh, '<', $tell);
81             File::Map::advise ($self->{'mmap'}, 'sequential');
82              
83             Scalar::Util::weaken ($cache{$key} = $self);
84             $self;
85             });
86             }
87             # return a scalar ref to the mmapped string
88             sub mmap_ref {
89 29     29 0 63 my ($self) = @_;
90 29         325 return \($self->{'mmap'});
91             }
92             sub DESTROY {
93 29     29   25256 my ($self) = @_;
94 29         575 delete $cache{$self->{'key'}};
95             }
96              
97             use constant::defer _PAGESIZE => sub {
98 4         1964 require POSIX;
99 4   50     25288 my $pagesize = eval { POSIX::sysconf (POSIX::_SC_PAGESIZE()) } || -1;
100 4 50       27 return ($pagesize > 0 ? $pagesize : 1024);
101 4     4   1884 };
  4         3224  
  4         38  
102              
103             # return the total bytes used by mmaps here plus prospective further $space
104             sub _total_space {
105 10     10   41 my ($space) = @_;
106             ### total space of: $space, values(%cache)
107 10         23 $space = _round_up_pagesize($space);
108 10         37 foreach my $self (values %cache) {
109 0         0 $space += _round_up_pagesize (length (${$self->mmap_ref}));
  0         0  
110             }
111 10         26 return $space;
112             }
113             sub _round_up_pagesize {
114 12     12   322 my ($n) = @_;
115              
116 12         38 my $pagesize = _PAGESIZE();
117 12         216 return $pagesize * int (($n + $pagesize - 1) / $pagesize);
118             }
119              
120             #-----------------------------------------------------------------------------
121              
122             # return true if $fh has an ":mmap" layer
123             sub _have_mmap_layer {
124 4     4   4050 my ($fh) = @_;
125 4         8 my $ret;
126 4         8 eval {
127 4         22 require PerlIO; # new in perl 5.8
128 4         29 foreach my $layer (PerlIO::get_layers ($fh)) {
129 11 100       30 if ($layer eq 'mmap') { $ret = 1; last; }
  1         2  
  1         3  
130             }
131             };
132 4         18 return $ret;
133             }
134              
135             # return true if mmapping $fh would be an excessive cumulative size
136             sub _mmap_size_excessive {
137 8     8   18 my ($fh) = @_;
138 8 50       42 if (File::Locate::Iterator::FileMap->find($fh)) {
139             # if already mapped then not excessive
140 0         0 return 0;
141             }
142              
143             # in 32-bits this is 4G*(1/4)*(1/5) which is 200Mb
144 8         45 require Config;
145             my $limit
146 8         328 = (2 ** (8 * $Config::Config{'ptrsize'})) # eg. 2^32 bytes addr space
147             * 0.25 # perhaps only 1/2 or 1/4 of it usable for data
148             * 0.2; # then don't go past 1/5 of that usable space
149              
150 8         105 my $prosp = File::Locate::Iterator::FileMap::_total_space (-s $fh);
151             ### mmap size limit: $limit
152             ### file size: -s $fh
153             ### for new total: $prosp
154 8         43 return ($prosp > $limit);
155             }
156              
157             1;
158             __END__