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 27     27   78 use strict;
  27         26  
  27         567  
11 27     27   76 use warnings;
  27         25  
  27         456  
12              
13 27     27   73 use Filesys::POSIX::Bits;
  27         24  
  27         6193  
14 27     27   9125 use Filesys::POSIX::Bits::System;
  27         44  
  27         562  
15 27     27   8914 use Filesys::POSIX::IO::Handle ();
  27         41  
  27         420  
16 27     27   96 use Filesys::POSIX::Error qw(throw);
  27         28  
  27         854  
17              
18 27     27   80 use Fcntl;
  27         26  
  27         4995  
19 27     27   102 use Carp ();
  27         29  
  27         303  
20 27     27   16303 use File::Temp ();
  27         362507  
  27         21663  
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 884 my ( $class, %opts ) = @_;
44              
45             return bless {
46             'fh' => undef,
47             'buf' => '',
48             'max' => defined $opts{'max'} ? $opts{'max'} : $DEFAULT_MAX,
49             'dir' => defined $opts{'dir'} ? $opts{'dir'} : $DEFAULT_DIR,
50 73 100       563 'inode' => $opts{'inode'},
    100          
51             'size' => 0,
52             'pos' => 0
53             }, $class;
54             }
55              
56             sub DESTROY {
57 12     12   3926 my ($self) = @_;
58              
59 12 100       40 close $self->{'fh'} if $self->{'fh'};
60              
61 12 100 100     123 if ( $self->{'file'} && -f $self->{'file'} ) {
62 4         221 unlink $self->{'file'};
63             }
64             }
65              
66             sub open {
67 97     97 0 222 my ( $self, $flags ) = @_;
68 97   100     179 $flags ||= 0;
69              
70 97 100       243 throw &Errno::EBUSY if $self->{'fh'};
71              
72 96         113 $self->{'pos'} = 0;
73              
74 96 100       292 if ( $flags & $O_APPEND ) {
    100          
75 4         6 $self->{'pos'} = $self->{'size'};
76             }
77             elsif ( $flags & ( $O_CREAT | $O_TRUNC ) ) {
78 73         71 $self->{'size'} = 0;
79 73         82 $self->{'inode'}->{'size'} = 0;
80              
81 73         78 undef $self->{'buf'};
82 73         84 $self->{'buf'} = '';
83             }
84              
85 96 100       159 if ( $self->{'file'} ) {
86 5         13 my $fcntl_flags = Filesys::POSIX::Bits::System::convertFlagsToSystem($flags);
87              
88 5 100       169 sysopen( my $fh, $self->{'file'}, $fcntl_flags ) or Carp::confess("$!");
89              
90 4         10 $self->{'fh'} = $fh;
91             }
92              
93 95         154 return $self;
94             }
95              
96             sub _flush_to_disk {
97 8     8   60 my ( $self, $len ) = @_;
98              
99 8 100       20 throw &Errno::EALREADY if $self->{'file'};
100              
101             my ( $fh, $file ) =
102 7         6 eval { File::Temp::mkstemp("$self->{'dir'}/.bucket-XXXXXX") };
  7         28  
103              
104 7 100       1997 Carp::confess("mkstemp() failure: $@") if $@;
105              
106 6         9 my $offset = 0;
107              
108 6         19 for ( my $left = $self->{'size'}; $left > 0; $left -= $len ) {
109 128 100       120 my $wrlen = $left > $len ? $len : $left;
110              
111 128         466 syswrite( $fh, substr( $self->{'buf'}, $offset, $wrlen ), $wrlen );
112              
113 128         184 $offset += $wrlen;
114             }
115              
116 6         11 @{$self}{qw(fh file)} = ( $fh, $file );
  6         23  
117             }
118              
119             sub write {
120 739     739 1 2904 my ( $self, $buf, $len ) = @_;
121 739         430 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       1013 if ( $self->{'pos'} + $len > $self->{'max'} ) {
129 72 100       100 $self->_flush_to_disk($len) unless $self->{'fh'};
130             }
131              
132 738 100       688 if ( $self->{'fh'} ) {
133             Carp::confess("Unable to write to disk bucket")
134 71 100       106 unless fileno( $self->{'fh'} );
135 70         491 $ret = syswrite( $self->{'fh'}, $buf );
136             }
137             else {
138 667 100       915 if ( ( my $gap = $self->{'pos'} - $self->{'size'} ) > 0 ) {
139 1         3 $self->{'buf'} .= "\x00" x $gap;
140             }
141              
142 667         674 substr( $self->{'buf'}, $self->{'pos'}, $len ) =
143             substr( $buf, 0, $len );
144 667         447 $ret = $len;
145             }
146              
147 737         587 $self->{'pos'} += $ret;
148 737         402 $self->{'size'} += $ret;
149              
150 737 100       882 if ( $self->{'pos'} > $self->{'size'} ) {
151 1         1 $self->{'size'} = $self->{'pos'};
152             }
153              
154 737         440 $self->{'inode'}->{'size'} = $self->{'size'};
155              
156 737         734 return $ret;
157             }
158              
159             sub read {
160 216     216 1 4038 my $self = shift;
161 216         134 my $len = pop;
162 216         132 my $ret = 0;
163              
164 216 100       224 if ( $self->{'fh'} ) {
165             Carp::confess("Unable to read bucket: $!")
166 198 100       253 unless fileno( $self->{'fh'} );
167 197         335 $ret = sysread( $self->{'fh'}, $_[0], $len );
168             }
169             else {
170             my $pos =
171 18 100       42 $self->{'pos'} > $self->{'size'} ? $self->{'size'} : $self->{'pos'};
172 18         20 my $maxlen = $self->{'size'} - $pos;
173 18 100       40 $len = $maxlen if $len > $maxlen;
174              
175 18 100       45 unless ($len) {
176 4         6 $_[0] = '';
177 4         12 return 0;
178             }
179              
180 14         45 $_[0] = substr( $self->{'buf'}, $self->{'pos'}, $len );
181 14         19 $ret = $len;
182             }
183              
184 211         154 $self->{'pos'} += $ret;
185              
186 211         207 return $ret;
187             }
188              
189             sub seek {
190 14     14 1 912 my ( $self, $pos, $whence ) = @_;
191 14         10 my $newpos;
192              
193 14 100       36 if ( $self->{'fh'} ) {
    100          
    100          
    100          
194 5         16 $newpos = sysseek( $self->{'fh'}, $pos, $whence );
195             }
196             elsif ( $whence == $SEEK_SET ) {
197 6         7 $newpos = $pos;
198             }
199             elsif ( $whence == $SEEK_CUR ) {
200 1         2 $newpos = $self->{'pos'} + $pos;
201             }
202             elsif ( $whence == $SEEK_END ) {
203 1         2 $newpos = $self->{'size'} + $pos;
204             }
205             else {
206 1         6 throw &Errno::EINVAL;
207             }
208              
209 13         37 return $self->{'pos'} = $newpos;
210             }
211              
212             sub tell {
213 5     5 1 7 my ($self) = @_;
214              
215 5 100       12 if ( $self->{'fh'} ) {
216 1         5 return sysseek $self->{'fh'}, 0, 1;
217             }
218              
219 4         12 return $self->{'pos'};
220             }
221              
222             sub close {
223 86     86 1 3279 my ($self) = @_;
224              
225 86 100       158 if ( $self->{'fh'} ) {
226 7         46 close $self->{'fh'};
227 7         13 undef $self->{'fh'};
228             }
229              
230 86         121 $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__