ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/syncmail/folder.pm
(Generate patch)

Comparing syncmail/folder.pm (file contents):
Revision 1.3 by root, Sun Oct 28 04:00:58 2001 UTC vs.
Revision 1.6 by root, Mon Oct 29 02:36:32 2001 UTC

1package folder; 1package folder;
2 2
3BEGIN { *slog = \&::slog }; 3BEGIN { *slog = \&::slog };
4 4
5use Digest::SHA1; 5use Fcntl;
6use File::Sync ();
7
8use Inline Config => NAME => "syncmail::folder";
9use Inline C;
6 10
7use constant MDIFVERSION => 1; 11use constant MDIFVERSION => 1;
12
13BEGIN {
14 if (1) {
15 use OpenSSL ();
16 *hash = \&OpenSSL::Digest::sha1_hex;
17 } elsif (0) {
18 # use Digest::SHA1 ();
19 my $digest = new Digest::SHA1;
20 *hash = sub {
21 $digest->reset;
22 $digest->add(@_);
23 $mid = $digest->hexdigest;
24 };
25 }
26}
27
28sub fflush {
29 my $oldfh = select $_[0];
30 $| = 1; $| = 0;
31 select $oldfh;
32}
33
34# rename a file and fsync the directory
35sub replace {
36 my ($src, $dst) = @_;
37 my $self = shift;
38
39 rename $src, $dst;
40
41 $dst =~ s/[^\/]*$/./;
42
43 # now sync the directory
44
45 open my $dir, "<", $dst
46 or die "$dst: $!";
47
48 File::Sync::fsync($dir);
49}
8 50
9sub new { 51sub new {
10 my $class = shift; 52 my $class = shift;
11 my %arg = @_; 53 my %arg = @_;
12 bless { 54 my $self = bless {
13 path => "$::PREFIX/$arg{name}", 55 path => "$::PREFIX/$arg{name}",
14 %arg, 56 %arg,
15 }, $class; 57 }, $class;
58 $self->open(0);
59 $self;
16} 60}
17 61
18sub dirty { 62sub dirty {
19 $_[0]{dirty} = 1; 63 $_[0]{dirty} = 1;
20} 64}
21 65
22sub DESTROY { 66sub DESTROY {
23 $_[0]->write_mdif; 67 #$_[0]->write_mdif; # do NOT!
24} 68}
25 69
26# parse_mbox(mbox-file-path, callback) 70# parse_mbox(mbox-file-path, callback)
27# callback gets called with \$header and \$body, 71# callback gets called with \$header and \$body,
28# $header includes the mbox From_ line without 72# $header includes the mbox From_ line without
29# the leading From_ itself. 73# the leading From_ itself.
30sub parse_mbox { 74sub parse_mbox {
31 my ($path, $cb) = @_; 75 my ($fh, $cb) = @_;
32
33 open my $fh, "<", $path
34 or die "$path: $!";
35 76
36 local $/ = "\n\n"; 77 local $/ = "\n\n";
37 78
38 my ($head, $body, $offs); 79 my ($head, $body, $offs);
39 80
40 5 == read $fh, $head, 5 81 read $fh, $head, 5;
41 or return;
42 82
43 $head eq "From " 83 $head eq "From " or $head eq ""
44 or return; 84 or return;
45 85
46 $offs = 0; 86 $offs = 0;
47 while (defined ($head = <$fh>)) { 87 while (defined ($head = <$fh>)) {
48 $head =~ /^.*? [A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] \d\d:\d\d:\d\d(?: [+-]\d\d\d\d)? \d\d(?:\d\d)\n/ 88 $head =~ /^.*? [A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] \d\d:\d\d:\d\d(?: [+-]\d\d\d\d)? \d\d(?:\d\d)\n/
57 $body = <$fh>; 97 $body = <$fh>;
58 #} 98 #}
59 chomp $body; 99 chomp $body;
60 $cb->($offs, \$head, \$body); 100 $cb->($offs, \$head, \$body);
61 $offs = (tell $fh) - 5; 101 $offs = (tell $fh) - 5;
62 ::give unless ++$ecnt & 1023; 102 &::give unless ++$ecnt & 255;
63 } 103 }
64 104
65 1; 105 1;
66} 106}
67 107
150 print $fh "[DIFF $_->[0]]\n"; 190 print $fh "[DIFF $_->[0]]\n";
151 print $fh "+$_\n" for @{$_->[1]}; 191 print $fh "+$_\n" for @{$_->[1]};
152 print $fh "-$_\n" for @{$_->[2]}; 192 print $fh "-$_\n" for @{$_->[2]};
153 } 193 }
154 194
195 fflush($fh);
196 File::Sync::fsync($fh);
155 close $fh 197 close $fh
156 or die "$path~: unable to create updated .mdif: $!"; 198 or die "$path~: unable to create updated .mdif: $!";
157 199
158 rename "$path~", $path; 200 replace("$path~", $path);
159 201
160 delete $self->{dirty}; 202 delete $self->{dirty};
161} 203}
162 204
163if (1) {
164 use OpenSSL ();
165 *hash = \&OpenSSL::Digest::sha1_hex;
166} elsif (0) {
167 # use Digest::SHA1;
168 my $digest = new Digest::SHA1;
169 *hash = sub {
170 $digest->reset;
171 $digest->add(@_);
172 $mid = $digest->hexdigest;
173 };
174}
175
176sub gendiff { 205sub gendiff {
177 my ($d1, $d2) = @_; 206 my ($self, $d1, $d2) = @_;
178 207
179 my (@add, @del); 208 my (@add, @del);
180 my (%d1, %d2); 209 my (%d1, %d2);
181 210
182 for (@$d2) { 211 for (@$d2) {
193 # add msgs in d2 but not in d1 222 # add msgs in d2 but not in d1
194 for (@$d2) { 223 for (@$d2) {
195 push @add, $_->[1] unless exists $d1{$_->[1]}; 224 push @add, $_->[1] unless exists $d1{$_->[1]};
196 } 225 }
197 226
227 push @{$self->{diff}}, [
228 $self->{ctime},
198 (\@add, \@del); 229 \@add, \@del,
230 ] if @add || @del;
231}
232
233sub open {
234 my ($self, $rw) = @_;
235
236 if (!$self->{fh} || $self->{rw} != $rw) {
237 $self->close;
238 $self->{rw} = $rw;
239 sysopen $self->{fh}, $self->{path},
240 O_CREAT | ($rw ? O_RDWR : O_RDONLY),
241 0666
242 or die "$self->{path}: $!";
243 0 == setlkw(fileno $self->{fh}, $rw ? 2 : 1)
244 or die "$self->{path}: $!";
245
246 $self->check;
247 }
248}
249
250sub close {
251 my $self = shift;
252
253 if ($self->{rw} && $self->{fh}) {
254 fflush($self->{fh});
255 File::Sync::fsync($self->{fh});
256 }
257
258 $self->write_mdif;
259
260 delete $self->{fh};
199} 261}
200 262
201sub check { 263sub check {
202 my $self = shift; 264 my $self = shift;
203 my $path = $self->{path};
204 my $conf = $self->conf_path; 265 my $conf = $self->conf_path;
205 my $guard = $::lockdisk->guard; 266 my $guard = $::lockdisk->guard;
206 267
207 slog 3, "checking $path\n"; 268 slog 3, "checking $self->{path}\n";
208 269
209 if (stat $path) {
210 my ($fsize, $mtime) = (stat _)[7, 9]; 270 my ($fsize, $mtime) = (stat $self->{fh})[7, 9];
211 271
272 if ($self->{idx}) {
273 return 1 if $fsize == $self->{fsize}
274 && $mtime == $self->{mtime};
275 } else {
212 if (open my $fh, "<", $conf) { 276 if (open my $fh, "<", $conf) {
213 my %conf; 277 my %conf;
214 <$fh>; # skip initial comment 278 <$fh>; # skip initial comment
215 <$fh> eq "[SYNCMAIL]\n" 279 <$fh> eq "[SYNCMAIL]\n"
216 or die "$conf: format error"; 280 or die "$conf: format error";
219 } 283 }
220 return 1 if $fsize == $conf{fsize} 284 return 1 if $fsize == $conf{fsize}
221 && $mtime == $conf{mtime}; 285 && $mtime == $conf{mtime};
222 286
223 $conf{mtime} <= $mtime 287 $conf{mtime} <= $mtime
224 or die "$path: folder older than mdif"; 288 or die "$self->{path}: folder older than mdif";
225 } 289 }
290 }
226 291
227 slog 2, "updating $path\n"; 292 slog 2, "updating $self->{path}\n";
228 293
229 my @idx; 294 my @idx;
230 295
296 seek $self->{fh}, 0, SEEK_SET;
297
231 parse_mbox $path, sub { 298 parse_mbox $self->{fh}, sub {
232 my ($offs, $head, $body) = @_; 299 my ($offs, $head, $body) = @_;
233 push @idx, [$offs, hash($$head, "\0", $$body)]; 300 push @idx, [$offs, hash($$head, "\n\n", $$body)];
234 } or return (); 301 } or die "$self->{path}: no valid mbox file";
235 302
236 $self->read_mdif; 303 $self->read_mdif;
237 304
238 $self->{version} ||= MDIFVERSION; 305 $self->{version} ||= MDIFVERSION;
306 $self->{ctime} = time;
307
308 $self->gendiff($self->{idx}, \@idx);
309
239 $self->{fsize} = $fsize; 310 $self->{fsize} = $fsize;
240 $self->{mtime} = $mtime; 311 $self->{mtime} = $mtime;
241 $self->{ctime} = time;
242 $self->{idx} = \@idx; 312 $self->{idx} = \@idx;
243 313
244 my ($add, $del) = gendiff $self->{idx}, \@idx;
245 push @{$self->{diff}}, [
246 $self->{ctime},
247 $add, $del,
248 ] if @$add || @$del;
249
250 $self->dirty; 314 $self->dirty;
315 $self->write_mdif;#d#
316}
251 317
252 return 2; 318sub inventory {
253 } else { 319 hash sort map { $_->[1] } @{$_[0]{idx}};
254 slog 2, "$path: no longer exists\n"; 320}
255 unlink $conf;
256 321
257 return (); 322sub iidx {
323 my $self = shift;
324
325 $self->{iidx} ||= do {
326 my %iidx;
327 my $idx = $self->{idx};
328
329 push @$idx, [$self->{fsize}];
330 my $ofs = 0;
331 for (0 .. @$idx - 2) {
332 $iidx{$idx->[$_][1]} = [$idx->[$_][0], $idx->[$_+1][0] - $idx->[$_][0]];
333 }
334 pop @$idx, [$self->{fsize}];
335
336 \%iidx;
258 } 337 };
338}
339
340sub exists {
341 $_[0]->iidx unless $_[0]{iidx};
342 return $_[0]{iidx}{$_[1]};
343}
344
345sub fetch {
346 my ($self, $hash) = @_;
347
348 $self->iidx unless $self->{iidx};
349
350 my $mail;
351
352 my $msg = $self->{iidx}{$hash}
353 or die "$hash: no such message in $self->{path}";
354
355 print STDERR "$self->{fh}, $msg->[0], SEEK_SET\n";#d#
356
357 seek $self->{fh}, $msg->[0], SEEK_SET
358 or die "$self->{path}: $!";
359
360 print STDERR "$msg->[1] == read $self->{fh}, $msg->[1]\n";#d#
361
362 $msg->[1] == read $self->{fh}, $mail, $msg->[1]
363 or die "$self->{path}: $!";
364
365 $mail =~ /^From \S/
366 or die "$self->{path}: mail folder corrupted";
367
368 $mail;
369}
370
371# begin updating folder
372sub begin_update {
373 my $self = shift;
374
375 $self->{oidx} = $self->{idx};
376}
377
378sub delete {
379 my $self = shift;
380 my $temp = "$self->{path}~";
381
382 $self->iidx unless $self->{iidx};
383
384 for (@_) {
385 if (exists $self->{iidx}{$_}) { # at least one message exists
386 my $guard = $::lockdisk->guard;
387 my %del; @del{@_} = ();
388 my @nidx;
389 my $dofs = 0;
390
391 open my $fh, "+>", $temp
392 or die "$temp: $!";
393
394 eval {
395 0 == setlkw(fileno $fh, 2)
396 or die "$self->{path}~: $!";
397
398 $self->{fsize} = 0; # we virtually truncated the file
399
400 for (@{delete $self->{idx}}) {
401 my $hash = $_->[1];
402 my $buf;
403
404 unless (exists $del{$hash}) {
405 my ($ofs, $len) = @{$self->{iidx}{$hash}};
406
407 $len or die;
408
409 seek $self->{fh}, $ofs, SEEK_SET
410 or die "$self->{path}: $!";
411
412 $len == read $self->{fh}, $buf, $len
413 or die "$self->{path}: $!";
414
415 $buf =~ /^From \S/
416 or die "$self->{path}: corrupted mail folder";
417
418 print $fh $buf
419 or die "$self->{path}: $!";
420
421 push @nidx, [$dofs, $hash];
422 $self->{iidx}{$hash}[0] = $dofs;
423 $dofs += $len;
424
425 &::give unless ++$ecnt & 255;
426 } else {
427 delete $self->{iidx}{$hash};
428 slog 0, "skipping/deleting $hash\n";
429 }
430 }
431 };
432
433 if ($@) {
434 close $fh;
435 unlink $temp;
436 die;
437 }
438
439 fflush($fh);
440 File::Sync::fsync($fh);
441 replace $temp, $self->{path};
442
443 $self->{fh} = $fh;
444 $self->{rw} = 1;
445
446 delete $self->{iidx};
447
448 $self->{idx} = \@nidx;
449 $self->{fsize} = $dofs;
450
451 return;
452 }
453 }
454}
455
456sub append {
457 my ($self, $hash, $mail) = @_;
458
459 if (length $mail) {
460 $self->open(1);
461
462 seek $self->{fh}, $self->{fsize}, SEEK_SET
463 or die "$self->{path}: $!";
464
465 print {$self->{fh}} $mail
466 or die "$self->{path}: $!";
467
468 push @{$self->{idx}}, [$self->{fsize}, $hash];
469 $self->{fsize} += length $mail;
470 }
471}
472
473sub end_update {
474 my $self = shift;
475
476 $self->gendiff((delete $self->{oidx}), $self->{idx});
477
478 fflush($self->{fh});
479 File::Sync::fsync($self->{fh});
480
481 stat $self->{fh}
482 or die "$self->{path}: $!";
483
484 $self->{fsize} = (stat _)[7];
485 $self->{mtime} = (stat _)[9];
486
487 $self->dirty;
259} 488}
260 489
2611; 4901;
262 491
492__DATA__
493__C__
494#include <unistd.h>
495#include <fcntl.h>
496
497/* mode0 unlock, mode1 rlock, mode2 rwlock */
498int setlkw(int fd, int mode)
499{
500 struct flock l;
501
502 l.l_type = mode == 0 ? F_UNLCK
503 : mode == 1 ? F_RDLCK
504 : F_WRLCK;
505 l.l_whence = SEEK_SET;
506 l.l_start = 0;
507 l.l_len = 0;
508
509 return fcntl (fd, F_SETLKW, &l);
510}
511

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines