The header now has its own sector. A lot needs to be moved over to it, but it's there.
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / File.pm
1 package DBM::Deep::File;
2
3 use 5.006_000;
4
5 use strict;
6 use warnings FATAL => 'all';
7
8 use Fcntl qw( :DEFAULT :flock :seek );
9
10 use constant DEBUG => 0;
11
12 sub new {
13     my $class = shift;
14     my ($args) = @_;
15
16     my $self = bless {
17         autobless          => 1,
18         autoflush          => 1,
19         end                => 0,
20         fh                 => undef,
21         file               => undef,
22         file_offset        => 0,
23         locking            => 1,
24         locked             => 0,
25 #XXX Migrate this to the engine, where it really belongs.
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
47 sub open {
48     my $self = shift;
49
50     # Adding O_BINARY should remove the need for the binmode below. However,
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.
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     }
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
79 sub 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
90 sub size {
91     my $self = shift;
92
93     return 0 unless $self->{fh};
94     return -s $self->{fh};
95 }
96
97 sub set_inode {
98     my $self = shift;
99
100     unless ( defined $self->{inode} ) {
101         my @stats = stat($self->{fh});
102         $self->{inode} = $stats[1];
103         $self->{end} = $stats[7];
104     }
105
106     return 1;
107 }
108
109 sub print_at {
110     my $self = shift;
111     my $loc  = shift;
112
113     local ($/,$\);
114
115     my $fh = $self->{fh};
116     if ( defined $loc ) {
117         seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
118     }
119
120     if ( DEBUG ) {
121         my $caller = join ':', (caller)[0,2];
122         my $len = length( join '', @_ );
123         warn "($caller) print_at( " . (defined $loc ? $loc : '<undef>') . ", $len )\n";
124     }
125
126     print( $fh @_ ) or die "Internal Error (print_at($loc)): $!\n";
127
128     return 1;
129 }
130
131 sub read_at {
132     my $self = shift;
133     my ($loc, $size) = @_;
134
135     local ($/,$\);
136
137     my $fh = $self->{fh};
138     if ( defined $loc ) {
139         seek( $fh, $loc + $self->{file_offset}, SEEK_SET );
140     }
141
142     if ( DEBUG ) {
143         my $caller = join ':', (caller)[0,2];
144         warn "($caller) read_at( " . (defined $loc ? $loc : '<undef>') . ", $size )\n";
145     }
146
147     my $buffer;
148     read( $fh, $buffer, $size);
149
150     return $buffer;
151 }
152
153 sub DESTROY {
154     my $self = shift;
155     return unless $self;
156
157     $self->close;
158
159     return;
160 }
161
162 sub request_space {
163     my $self = shift;
164     my ($size) = @_;
165
166     #XXX Do I need to reset $self->{end} here? I need a testcase
167     my $loc = $self->{end};
168     $self->{end} += $size;
169
170     return $loc;
171 }
172
173 ##
174 # If db locking is set, flock() the db file.  If called multiple
175 # times before unlock(), then the same number of unlocks() must
176 # be called before the lock is released.
177 ##
178 sub lock_exclusive {
179     my $self = shift;
180     my ($obj) = @_;
181     return $self->lock( $obj, LOCK_EX );
182 }
183
184 sub lock_shared {
185     my $self = shift;
186     my ($obj) = @_;
187     return $self->lock( $obj, LOCK_SH );
188 }
189
190 sub lock {
191     my $self = shift;
192     my ($obj, $type) = @_;
193
194     $type = LOCK_EX unless defined $type;
195
196     #XXX This is a temporary fix for Win32 and autovivification. It
197     # needs to improve somehow. -RobK, 2008-03-09
198     if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
199         $type = LOCK_EX;
200     }
201
202     if (!defined($self->{fh})) { return; }
203
204     #XXX This either needs to allow for upgrading a shared lock to an
205     # exclusive lock or something else with autovivification.
206     # -RobK, 2008-03-09
207     if ($self->{locking}) {
208         if (!$self->{locked}) {
209             flock($self->{fh}, $type);
210
211             # refresh end counter in case file has changed size
212             my @stats = stat($self->{fh});
213             $self->{end} = $stats[7];
214
215             # double-check file inode, in case another process
216             # has optimize()d our file while we were waiting.
217             if (defined($self->{inode}) && $stats[1] != $self->{inode}) {
218                 $self->close;
219                 $self->open;
220
221                 #XXX This needs work
222                 $obj->{engine}->setup_fh( $obj );
223
224                 flock($self->{fh}, $type); # re-lock
225
226                 # This may not be necessary after re-opening
227                 $self->{end} = (stat($self->{fh}))[7]; # re-end
228             }
229         }
230         $self->{locked}++;
231
232         return 1;
233     }
234
235     return;
236 }
237
238 ##
239 # If db locking is set, unlock the db file.  See note in lock()
240 # regarding calling lock() multiple times.
241 ##
242 sub unlock {
243     my $self = shift;
244
245     if (!defined($self->{fh})) { return; }
246
247     if ($self->{locking} && $self->{locked} > 0) {
248         $self->{locked}--;
249
250         if (!$self->{locked}) {
251             flock($self->{fh}, LOCK_UN);
252             return 1;
253         }
254
255         return;
256     }
257
258     return;
259 }
260
261 sub flush {
262     my $self = shift;
263
264     # Flush the filehandle
265     my $old_fh = select $self->{fh};
266     my $old_af = $|; $| = 1; $| = $old_af;
267     select $old_fh;
268
269     return 1;
270 }
271
272 # Taken from http://www.perlmonks.org/?node_id=691054
273 sub is_writable {
274     my $self = shift;
275
276     my $fh = $self->{fh};
277     return unless defined $fh;
278     return unless defined fileno $fh;
279     local $\ = '';  # just in case
280     no warnings;    # temporarily disable warnings
281     local $^W;      # temporarily disable warnings
282     return print $fh '';
283 }
284
285 sub copy_stats {
286     my $self = shift;
287     my ($temp_filename) = @_;
288
289     my @stats = stat( $self->{fh} );
290     my $perms = $stats[2] & 07777;
291     my $uid = $stats[4];
292     my $gid = $stats[5];
293     chown( $uid, $gid, $temp_filename );
294     chmod( $perms, $temp_filename );
295 }
296
297 1;
298 __END__