Checkin fixing RT#30144
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / File.pm
CommitLineData
460b1067 1package DBM::Deep::File;
2
2120a181 3use 5.006_000;
460b1067 4
5use strict;
6use warnings;
7
08164b50 8our $VERSION = q(1.0012);
460b1067 9
86867f3a 10use Fcntl qw( :DEFAULT :flock :seek );
460b1067 11
12sub new {
13 my $class = shift;
14 my ($args) = @_;
15
16 my $self = bless {
359a01ac 17 autobless => 1,
2120a181 18 autoflush => 1,
460b1067 19 end => 0,
20 fh => undef,
21 file => undef,
22 file_offset => 0,
2120a181 23 locking => 1,
460b1067 24 locked => 0,
2120a181 25#XXX Migrate this to the engine, where it really belongs.
460b1067 26 filter_store_key => undef,
27 filter_store_value => undef,
28 filter_fetch_key => undef,
29 filter_fetch_value => undef,
30 }, $class;
31
32 # Grab the parameters we want to use
33 foreach my $param ( keys %$self ) {
34 next unless exists $args->{$param};
35 $self->{$param} = $args->{$param};
36 }
37
38 if ( $self->{fh} && !$self->{file_offset} ) {
39 $self->{file_offset} = tell( $self->{fh} );
40 }
41
42 $self->open unless $self->{fh};
43
44 return $self;
45}
46
47sub open {
48 my $self = shift;
49
633df1fd 50 # Adding O_BINARY should remove the need for the binmode below. However,
460b1067 51 # I'm not going to remove it because I don't have the Win32 chops to be
52 # absolutely certain everything will be ok.
e9b0b5f0 53 my $flags = O_CREAT | O_BINARY;
54
55 if ( !-e $self->{file} || -w _ ) {
56 $flags |= O_RDWR;
57 }
58 else {
59 $flags |= O_RDONLY;
60 }
460b1067 61
62 my $fh;
63 sysopen( $fh, $self->{file}, $flags )
64 or die "DBM::Deep: Cannot sysopen file '$self->{file}': $!\n";
65 $self->{fh} = $fh;
66
67 # Even though we use O_BINARY, better be safe than sorry.
68 binmode $fh;
69
70 if ($self->{autoflush}) {
71 my $old = select $fh;
72 $|=1;
73 select $old;
74 }
75
76 return 1;
77}
78
79sub close {
80 my $self = shift;
81
82 if ( $self->{fh} ) {
83 close $self->{fh};
84 $self->{fh} = undef;
85 }
86
87 return 1;
88}
89
7dcefff3 90sub set_inode {
91 my $self = shift;
92
2120a181 93 unless ( defined $self->{inode} ) {
7dcefff3 94 my @stats = stat($self->{fh});
95 $self->{inode} = $stats[1];
96 $self->{end} = $stats[7];
97 }
98
99 return 1;
100}
101
019404df 102sub print_at {
103 my $self = shift;
104 my $loc = shift;
105
106 local ($/,$\);
107
108 my $fh = $self->{fh};
7dcefff3 109 if ( defined $loc ) {
110 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
111 }
112
45f047f8 113 print( $fh @_ ) or die "Internal Error (print_at($loc)): $!\n";
019404df 114
115 return 1;
116}
117
7dcefff3 118sub read_at {
119 my $self = shift;
120 my ($loc, $size) = @_;
121
122 local ($/,$\);
123
124 my $fh = $self->{fh};
125 if ( defined $loc ) {
126 seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
127 }
128
129 my $buffer;
130 read( $fh, $buffer, $size);
131
132 return $buffer;
133}
134
460b1067 135sub DESTROY {
136 my $self = shift;
137 return unless $self;
138
139 $self->close;
140
141 return;
142}
143
019404df 144sub request_space {
145 my $self = shift;
146 my ($size) = @_;
147
7dcefff3 148 #XXX Do I need to reset $self->{end} here? I need a testcase
019404df 149 my $loc = $self->{end};
150 $self->{end} += $size;
151
152 return $loc;
153}
154
15ba72cc 155##
156# If db locking is set, flock() the db file. If called multiple
157# times before unlock(), then the same number of unlocks() must
158# be called before the lock is released.
159##
160sub lock {
161 my $self = shift;
162 my ($obj, $type) = @_;
42717e46 163
15ba72cc 164 $type = LOCK_EX unless defined $type;
165
45f047f8 166 #XXX This is a temporary fix for Win32 and autovivification. It
167 # needs to improve somehow. -RobK, 2008-03-09
168 if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
169 $type = LOCK_EX;
170 }
171
15ba72cc 172 if (!defined($self->{fh})) { return; }
173
45f047f8 174 #XXX This either needs to allow for upgrading a shared lock to an
175 # exclusive lock or something else with autovivification.
176 # -RobK, 2008-03-09
15ba72cc 177 if ($self->{locking}) {
178 if (!$self->{locked}) {
179 flock($self->{fh}, $type);
180
181 # refresh end counter in case file has changed size
182 my @stats = stat($self->{fh});
183 $self->{end} = $stats[7];
184
185 # double-check file inode, in case another process
186 # has optimize()d our file while we were waiting.
2120a181 187 if (defined($self->{inode}) && $stats[1] != $self->{inode}) {
15ba72cc 188 $self->close;
189 $self->open;
190
191 #XXX This needs work
192 $obj->{engine}->setup_fh( $obj );
193
194 flock($self->{fh}, $type); # re-lock
195
196 # This may not be necessary after re-opening
197 $self->{end} = (stat($self->{fh}))[7]; # re-end
198 }
199 }
200 $self->{locked}++;
201
202 return 1;
203 }
204
205 return;
206}
207
208##
209# If db locking is set, unlock the db file. See note in lock()
210# regarding calling lock() multiple times.
211##
212sub unlock {
213 my $self = shift;
214
215 if (!defined($self->{fh})) { return; }
216
217 if ($self->{locking} && $self->{locked} > 0) {
218 $self->{locked}--;
219 if (!$self->{locked}) { flock($self->{fh}, LOCK_UN); }
220
221 return 1;
222 }
223
224 return;
225}
226
2120a181 227sub flush {
25c7c8d6 228 my $self = shift;
229
2120a181 230 # Flush the filehandle
231 my $old_fh = select $self->{fh};
232 my $old_af = $|; $| = 1; $| = $old_af;
233 select $old_fh;
25c7c8d6 234
235 return 1;
236}
28394a1a 237
08164b50 238# Taken from http://www.perlmonks.org/?node_id=691054
6e6789b0 239sub is_writable {
240 my $self = shift;
08164b50 241
242 my $fh = $self->{fh};
243 return unless defined $fh;
244 return unless defined fileno $fh;
245 local $\ = ''; # just in case
246 no warnings; # temporarily disable warnings
247 local $^W; # temporarily disable warnings
248 return print $fh '';
6e6789b0 249}
250
251sub copy_stats {
252 my $self = shift;
253 my ($temp_filename) = @_;
254
255 my @stats = stat( $self->{fh} );
256 my $perms = $stats[2] & 07777;
257 my $uid = $stats[4];
258 my $gid = $stats[5];
259 chown( $uid, $gid, $temp_filename );
260 chmod( $perms, $temp_filename );
261}
262
460b1067 2631;
264__END__