File Coverage

blib/lib/Filesys/POSIX/Mem/Bucket.pm
Criterion Covered Total %
statement 111 111 100.0
branch 58 58 100.0
condition 5 5 100.0
subroutine 18 18 100.0
pod 6 7 85.7
total 198 199 99.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::Mem::Bucket;
9              
10 26     26   98 use strict;
  26         29  
  26         1068  
11 26     26   98 use warnings;
  26         28  
  26         642  
12              
13 26     26   107 use Filesys::POSIX::Bits;
  26         26  
  26         7419  
14 26     26   9674 use Filesys::POSIX::Bits::System;
  26         52  
  26         681  
15 26     26   9478 use Filesys::POSIX::IO::Handle ();
  26         60  
  26         551  
16 26     26   132 use Filesys::POSIX::Error qw(throw);
  26         31  
  26         1053  
17              
18 26     26   105 use Fcntl;
  26         26  
  26         5905  
19 26     26   125 use Carp ();
  26         29  
  26         344  
20 26     26   17741 use File::Temp ();
  26         468329  
  26         24342  
21              
22             =head1 NAME
23              
24             Filesys::POSIX::Mem::Bucket - Regular file I/O handle
25              
26             =head1 DESCRIPTION
27              
28             C provides an implementation of the interface in
29             L that allows access to the regular file data of a
30             file in a L filesystem hierarchy.
31              
32             Internally, the bucket can store up to a specified maximum number of bytes until
33             said data is flushed to a temporary file on disk, backed by L.
34              
35             =cut
36              
37             our @ISA = ('Filesys::POSIX::IO::Handle');
38              
39             my $DEFAULT_MAX = 16384;
40             my $DEFAULT_DIR = '/tmp';
41              
42             sub new {
43 73     73 1 1218 my ( $class, %opts ) = @_;
44              
45 73 100       752 return bless {
    100          
46             'fh' => undef,
47             'buf' => '',
48             'max' => defined $opts{'max'} ? $opts{'max'} : $DEFAULT_MAX,
49             'dir' => defined $opts{'dir'} ? $opts{'dir'} : $DEFAULT_DIR,
50             'inode' => $opts{'inode'},
51             'size' => 0,
52             'pos' => 0
53             }, $class;
54             }
55              
56             sub DESTROY {
57 12     12   6322 my ($self) = @_;
58              
59 12 100       61 close $self->{'fh'} if $self->{'fh'};
60              
61 12 100 100     155 if ( $self->{'file'} && -f $self->{'file'} ) {
62 4         360 unlink $self->{'file'};
63             }
64             }
65              
66             sub open {
67 97     97 0 319 my ( $self, $flags ) = @_;
68 97   100     216 $flags ||= 0;
69              
70 97 100       297 throw &Errno::EBUSY if $self->{'fh'};
71              
72 96         133 $self->{'pos'} = 0;
73              
74 96 100       306 if ( $flags & $O_APPEND ) {
    100          
75 4         10 $self->{'pos'} = $self->{'size'};
76             }
77             elsif ( $flags & ( $O_CREAT | $O_TRUNC ) ) {
78 73         101 $self->{'size'} = 0;
79 73         103 $self->{'inode'}->{'size'} = 0;
80              
81 73         100 undef $self->{'buf'};
82 73         100 $self->{'buf'} = '';
83             }
84              
85 96 100       207 if ( $self->{'file'} ) {
86 5         21 my $fcntl_flags = Filesys::POSIX::Bits::System::convertFlagsToSystem($flags);
87              
88 5 100       241 sysopen( my $fh, $self->{'file'}, $fcntl_flags ) or Carp::confess("$!");
89              
90 4         16 $self->{'fh'} = $fh;
91             }
92              
93 95         194 return $self;
94             }
95              
96             sub _flush_to_disk {
97 8     8   76 my ( $self, $len ) = @_;
98              
99 8 100       27 throw &Errno::EALREADY if $self->{'file'};
100              
101             my ( $fh, $file ) =
102 7         12 eval { File::Temp::mkstemp("$self->{'dir'}/.bucket-XXXXXX") };
  7         40  
103              
104 7 100       3570 Carp::confess("mkstemp() failure: $@") if $@;
105              
106 6         12 my $offset = 0;
107              
108 6         32 for ( my $left = $self->{'size'}; $left > 0; $left -= $len ) {
109 128 100       218 my $wrlen = $left > $len ? $len : $left;
110              
111 128         986 syswrite( $fh, substr( $self->{'buf'}, $offset, $wrlen ), $wrlen );
112              
113 128         349 $offset += $wrlen;
114             }
115              
116 6         10 @{$self}{qw(fh file)} = ( $fh, $file );
  6         35  
117             }
118              
119             sub write {
120 739     739 1 3582 my ( $self, $buf, $len ) = @_;
121 739         546 my $ret = 0;
122              
123             #
124             # If the current file position, plus the length of the intended write
125             # is to exceed the maximum memory bucket threshold, then dump the file
126             # to disk if it hasn't already happened.
127             #
128 739 100       1330 if ( $self->{'pos'} + $len > $self->{'max'} ) {
129 72 100       187 $self->_flush_to_disk($len) unless $self->{'fh'};
130             }
131              
132 738 100       1168 if ( $self->{'fh'} ) {
133 71 100       185 Carp::confess("Unable to write to disk bucket")
134             unless fileno( $self->{'fh'} );
135 70         863 $ret = syswrite( $self->{'fh'}, $buf );
136             }
137             else {
138 667 100       1150 if ( ( my $gap = $self->{'pos'} - $self->{'size'} ) > 0 ) {
139 1         5 $self->{'buf'} .= "\x00" x $gap;
140             }
141              
142 667         848 substr( $self->{'buf'}, $self->{'pos'}, $len ) =
143             substr( $buf, 0, $len );
144 667         569 $ret = $len;
145             }
146              
147 737         593 $self->{'pos'} += $ret;
148 737         701 $self->{'size'} += $ret;
149              
150 737 100       1280 if ( $self->{'pos'} > $self->{'size'} ) {
151 1         3 $self->{'size'} = $self->{'pos'};
152             }
153              
154 737         798 $self->{'inode'}->{'size'} = $self->{'size'};
155              
156 737         1033 return $ret;
157             }
158              
159             sub read {
160 216     216 1 4166 my $self = shift;
161 216         237 my $len = pop;
162 216         219 my $ret = 0;
163              
164 216 100       424 if ( $self->{'fh'} ) {
165 198 100       473 Carp::confess("Unable to read bucket: $!")
166             unless fileno( $self->{'fh'} );
167 197         772 $ret = sysread( $self->{'fh'}, $_[0], $len );
168             }
169             else {
170 18 100       70 my $pos =
171             $self->{'pos'} > $self->{'size'} ? $self->{'size'} : $self->{'pos'};
172 18         28 my $maxlen = $self->{'size'} - $pos;
173 18 100       43 $len = $maxlen if $len > $maxlen;
174              
175 18 100       38 unless ($len) {
176 4         7 $_[0] = '';
177 4         16 return 0;
178             }
179              
180 14         68 $_[0] = substr( $self->{'buf'}, $self->{'pos'}, $len );
181 14         22 $ret = $len;
182             }
183              
184 211         259 $self->{'pos'} += $ret;
185              
186 211         414 return $ret;
187             }
188              
189             sub seek {
190 14     14 1 1142 my ( $self, $pos, $whence ) = @_;
191 14         19 my $newpos;
192              
193 14 100       81 if ( $self->{'fh'} ) {
    100          
    100          
    100          
194 5         169 $newpos = sysseek( $self->{'fh'}, $pos, $whence );
195             }
196             elsif ( $whence == $SEEK_SET ) {
197 6         8 $newpos = $pos;
198             }
199             elsif ( $whence == $SEEK_CUR ) {
200 1         4 $newpos = $self->{'pos'} + $pos;
201             }
202             elsif ( $whence == $SEEK_END ) {
203 1         4 $newpos = $self->{'size'} + $pos;
204             }
205             else {
206 1         10 throw &Errno::EINVAL;
207             }
208              
209 13         52 return $self->{'pos'} = $newpos;
210             }
211              
212             sub tell {
213 5     5 1 7 my ($self) = @_;
214              
215 5 100       16 if ( $self->{'fh'} ) {
216 1         6 return sysseek $self->{'fh'}, 0, 1;
217             }
218              
219 4         16 return $self->{'pos'};
220             }
221              
222             sub close {
223 78     78 1 2812 my ($self) = @_;
224              
225 78 100       173 if ( $self->{'fh'} ) {
226 7         73 close $self->{'fh'};
227 7         17 undef $self->{'fh'};
228             }
229              
230 78         147 $self->{'pos'} = 0;
231             }
232              
233             =head1 SEE ALSO
234              
235             =over
236              
237             =item L
238              
239             =back
240              
241             =cut
242              
243             1;
244              
245             __END__