File Coverage

blib/lib/DBM/Deep/Storage/File.pm
Criterion Covered Total %
statement 141 146 96.5
branch 38 48 79.1
condition 12 15 80.0
subroutine 22 23 95.6
pod 14 14 100.0
total 227 246 92.2


line stmt bran cond sub pod time code
1             package DBM::Deep::Storage::File;
2              
3 54     54   945 use 5.008_004;
  54         202  
4              
5 54     54   285 use strict;
  54         91  
  54         1753  
6 54     54   311 use warnings FATAL => 'all';
  54         98  
  54         3642  
7              
8 54     54   329 use Fcntl qw( :DEFAULT :flock :seek );
  54         118  
  54         27631  
9              
10 54     54   457 use constant DEBUG => 0;
  54         153  
  54         4666  
11              
12 54     54   330 use base 'DBM::Deep::Storage';
  54         109  
  54         29767  
13              
14             =head1 NAME
15              
16             DBM::Deep::Storage::File - mediate low-level interaction with storage mechanism
17              
18             =head1 PURPOSE
19              
20             This is an internal-use-only object for L. It mediates the low-level
21             interaction with the storage mechanism.
22              
23             Currently, the only storage mechanism supported is the file system.
24              
25             =head1 OVERVIEW
26              
27             This class provides an abstraction to the storage mechanism so that the Engine
28             (the only class that uses this class) doesn't have to worry about that.
29              
30             =head1 METHODS
31              
32             =head2 new( \%args )
33              
34             =cut
35              
36             sub new {
37 405     405 1 720 my $class = shift;
38 405         879 my ($args) = @_;
39              
40 405         3686 my $self = bless {
41             autobless => 1,
42             autoflush => 1,
43             end => 0,
44             fh => undef,
45             file => undef,
46             file_offset => 0,
47             locking => 1,
48             locked => 0,
49             #XXX Migrate this to the engine, where it really belongs.
50             filter_store_key => undef,
51             filter_store_value => undef,
52             filter_fetch_key => undef,
53             filter_fetch_value => undef,
54             }, $class;
55              
56             # Grab the parameters we want to use
57 405         2158 foreach my $param ( keys %$self ) {
58 4860 100       9654 next unless exists $args->{$param};
59 470         1175 $self->{$param} = $args->{$param};
60             }
61              
62 405 100 100     1653 if ( $self->{fh} && !$self->{file_offset} ) {
63 5         21 $self->{file_offset} = tell( $self->{fh} );
64             }
65              
66 405 100       1938 $self->open unless $self->{fh};
67              
68 404         1593 return $self;
69             }
70              
71             =head2 open()
72              
73             This method opens the filehandle for the filename in C< file >.
74              
75             There is no return value.
76              
77             =cut
78              
79             # TODO: What happens if we ->open when we already have a $fh?
80             sub open {
81 414     414 1 740 my $self = shift;
82              
83             # Adding O_BINARY should remove the need for the binmode below. However,
84             # I'm not going to remove it because I don't have the Win32 chops to be
85             # absolutely certain everything will be ok.
86 414         680 my $flags = O_CREAT | O_BINARY;
87              
88 414 50 66     12074 if ( !-e $self->{file} || -w _ ) {
89 414         1054 $flags |= O_RDWR;
90             }
91             else {
92 0         0 $flags |= O_RDONLY;
93             }
94              
95 414         838 my $fh;
96 414 100       19927 sysopen( $fh, $self->{file}, $flags )
97             or die "DBM::Deep: Cannot sysopen file '$self->{file}': $!\n";
98 413         1460 $self->{fh} = $fh;
99              
100             # Even though we use O_BINARY, better be safe than sorry.
101 413         1295 binmode $fh;
102              
103 413 50       1287 if ($self->{autoflush}) {
104 413         1733 my $old = select $fh;
105 413         1282 $|=1;
106 413         1365 select $old;
107             }
108              
109 413         839 return 1;
110             }
111              
112             =head2 close()
113              
114             If the filehandle is opened, this will close it.
115              
116             There is no return value.
117              
118             =cut
119              
120             sub close {
121 432     432 1 675 my $self = shift;
122              
123 432 100       1236 if ( $self->{fh} ) {
124 410         6128 close $self->{fh};
125 410         1590 $self->{fh} = undef;
126             }
127              
128 432         851 return 1;
129             }
130              
131             =head2 size()
132              
133             This will return the size of the DB. If file_offset is set, this will take that into account.
134              
135             B: This function isn't used internally anywhere.
136              
137             =cut
138              
139             sub size {
140 0     0 1 0 my $self = shift;
141              
142 0 0       0 return 0 unless $self->{fh};
143 0         0 return( (-s $self->{fh}) - $self->{file_offset} );
144             }
145              
146             =head2 set_inode()
147              
148             This will set the inode value of the underlying file object.
149              
150             This is only needed to handle some obscure Win32 bugs. It really shouldn't be
151             needed outside this object.
152              
153             There is no return value.
154              
155             =cut
156              
157             sub set_inode {
158 2953     2953 1 4780 my $self = shift;
159              
160 2953 100       8033 unless ( defined $self->{inode} ) {
161 393         5127 my @stats = stat($self->{fh});
162 393         1084 $self->{inode} = $stats[1];
163 393         914 $self->{end} = $stats[7];
164             }
165              
166 2953         5794 return 1;
167             }
168              
169             =head2 print_at( $offset, @data )
170              
171             This takes an offset and some data to print.
172              
173             C< $offset > will be used to seek into the file. If file_offset is
174             set, it will be used as the zero location. If it is undefined, no seeking will
175             occur. Then, C< @data > will be printed to the current location.
176              
177             There is no return value.
178              
179             If writing to the file would make the file too big for the C that
180             is a fatal error.
181              
182             =cut
183              
184             sub print_at {
185 37754     37754 1 55706 my $self = shift;
186 37754         52037 my $loc = shift;
187              
188 37754         103676 local ($,,$\);
189              
190 37754         62045 my $fh = $self->{fh};
191 37754         82786 my $len = length( join '', @_ );
192              
193 37754         261037 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
194              
195 37754 100       131282 if(tell($fh) > $len + 2 ** (8 * $self->{byte_size}) - 1) {
196 8         142 die("DBM::Deep: too much data, try a bigger pack_size\n");
197             }
198              
199 37746         48865 if ( DEBUG ) {
200             my $caller = join ':', (caller)[0,2];
201             warn "($caller) print_at( " . (defined $loc ? $loc : '') . ", $len )\n";
202             }
203              
204 37746 50       787654 print( $fh @_ ) or die "Internal Error (print_at($loc)): $!\n";
205              
206 37746         178154 return 1;
207             }
208              
209             =head2 read_at( $offset, $length )
210              
211             This takes an optional offset and a length.
212              
213             C< $offset >, if defined, will be used to seek into the file. If file_offset is
214             set, it will be used as the zero location. If it is undefined, no seeking will
215             occur. Then, C< $length > bytes will be read from the current location.
216              
217             The data read will be returned.
218              
219             =cut
220              
221             sub read_at {
222 135874     135874 1 217063 my $self = shift;
223 135874         255152 my ($loc, $size) = @_;
224              
225 135874         245150 my $fh = $self->{fh};
226 135874 100       282625 if ( defined $loc ) {
227 135276         1231770 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
228             }
229              
230 135874         214634 if ( DEBUG ) {
231             my $caller = join ':', (caller)[0,2];
232             warn "($caller) read_at( " . (defined $loc ? $loc : '') . ", $size )\n";
233             }
234              
235 135874         192459 my $buffer;
236 135874         1125839 read( $fh, $buffer, $size);
237              
238 135874         570978 return $buffer;
239             }
240              
241             =head2 DESTROY
242              
243             When the ::Storage::File object goes out of scope, it will be closed.
244              
245             =cut
246              
247             sub DESTROY {
248 397     397   799 my $self = shift;
249 397 50       1023 return unless $self;
250              
251 397         1730 $self->close;
252              
253 397         6512 return;
254             }
255              
256             =head2 request_space( $size )
257              
258             This takes a size and adds that much space to the DBM.
259              
260             This returns the offset for the new location.
261              
262             =cut
263              
264             sub request_space {
265 7467     7467 1 10935 my $self = shift;
266 7467         12649 my ($size) = @_;
267              
268             #XXX Do I need to reset $self->{end} here? I need a testcase
269 7467         11706 my $loc = $self->{end};
270 7467         12303 $self->{end} += $size;
271              
272 7467         15248 return $loc;
273             }
274              
275             =head2 copy_stats( $target_filename )
276              
277             This will take the stats for the current filehandle and apply them to
278             C< $target_filename >. The stats copied are:
279              
280             =over 4
281              
282             =item * Onwer UID and GID
283              
284             =item * Permissions
285              
286             =back
287              
288             =cut
289              
290             sub copy_stats {
291 2     2 1 6 my $self = shift;
292 2         6 my ($temp_filename) = @_;
293              
294 2         32 my @stats = stat( $self->{fh} );
295 2         7 my $perms = $stats[2] & 07777;
296 2         4 my $uid = $stats[4];
297 2         4 my $gid = $stats[5];
298 2         63 chown( $uid, $gid, $temp_filename );
299 2         31 chmod( $perms, $temp_filename );
300             }
301              
302             sub flush {
303 6365     6365 1 10297 my $self = shift;
304              
305             # Flush the filehandle
306 6365         26559 my $old_fh = select $self->{fh};
307 6365         18213 my $old_af = $|; $| = 1; $| = $old_af;
  6365         13428  
  6365         11092  
308 6365         19987 select $old_fh;
309              
310 6365         14866 return 1;
311             }
312              
313             sub is_writable {
314 3170     3170 1 5909 my $self = shift;
315              
316 3170         5557 my $fh = $self->{fh};
317 3170 50       6839 return unless defined $fh;
318 3170 50       9361 return unless defined fileno $fh;
319 3170         13812 local $\ = ''; # just in case
320 54     54   490 no warnings; # temporarily disable warnings
  54         251  
  54         28195  
321 3170         10539 local $^W; # temporarily disable warnings
322 3170         26479 return print $fh '';
323             }
324              
325             sub lock_exclusive {
326 8376     8376 1 12683 my $self = shift;
327 8376         19827 my ($obj) = @_;
328 8376         22236 return $self->_lock( $obj, LOCK_EX );
329             }
330              
331             sub lock_shared {
332 6809     6809 1 11551 my $self = shift;
333 6809         12788 my ($obj) = @_;
334 6809         17256 return $self->_lock( $obj, LOCK_SH );
335             }
336              
337             sub _lock {
338 15185     15185   22749 my $self = shift;
339 15185         28326 my ($obj, $type) = @_;
340              
341 15185 50       34379 $type = LOCK_EX unless defined $type;
342              
343             #XXX This is a temporary fix for Win32 and autovivification. It
344             # needs to improve somehow. -RobK, 2008-03-09
345 15185 50 33     82195 if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
346 0         0 $type = LOCK_EX;
347             }
348              
349 15185 100       39237 if (!defined($self->{fh})) { return; }
  1         6  
350              
351             #XXX This either needs to allow for upgrading a shared lock to an
352             # exclusive lock or something else with autovivification.
353             # -RobK, 2008-03-09
354 15184 100       37329 if ($self->{locking}) {
355 15181 100       36152 if (!$self->{locked}) {
356 6277         61226 flock($self->{fh}, $type);
357              
358             # refresh end counter in case file has changed size
359 6277         95793 my @stats = stat($self->{fh});
360 6277         15414 $self->{end} = $stats[7];
361              
362             # double-check file inode, in case another process
363             # has optimize()d our file while we were waiting.
364 6277 100 100     34930 if (defined($self->{inode}) && $stats[1] != $self->{inode}) {
365 12         45 $self->close;
366 12         38 $self->open;
367              
368             #XXX This needs work
369 12         58 $obj->{engine}->setup( $obj );
370              
371 12         143 flock($self->{fh}, $type); # re-lock
372              
373             # This may not be necessary after re-opening
374 12         114 $self->{end} = (stat($self->{fh}))[7]; # re-end
375             }
376             }
377 15181         27215 $self->{locked}++;
378              
379 15181         46594 return 1;
380             }
381              
382 3         7 return;
383             }
384              
385             sub unlock {
386 15179     15179 1 24621 my $self = shift;
387              
388 15179 100       36760 if (!defined($self->{fh})) { return; }
  6         14  
389              
390 15173 100 100     63598 if ($self->{locking} && $self->{locked} > 0) {
391 15167         30304 $self->{locked}--;
392              
393 15167 100       32457 if (!$self->{locked}) {
394 6266         70518 flock($self->{fh}, LOCK_UN);
395 6266         18797 return 1;
396             }
397              
398 8901         21369 return;
399             }
400              
401 6         15 return;
402             }
403              
404             1;
405             __END__