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

Comparing syncmail/folder.pm (file contents):
Revision 1.2 by root, Sun Oct 28 03:51:24 2001 UTC vs.
Revision 1.5 by root, Mon Oct 29 00:37:41 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 flushfh {
29 my $oldfh = select $_[0];
30 $| = 1;
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
134 or die "$path~: $!"; 174 or die "$path~: $!";
135 175
136 print $fh "# automatically generated, do NOT edit\n"; 176 print $fh "# automatically generated, do NOT edit\n";
137 177
138 print $fh "[SYNCMAIL]\n"; 178 print $fh "[SYNCMAIL]\n";
139 print $fh "$_=$self->{$_}\n" for (qw(fsize mtime version)); 179 print $fh "$_=$self->{$_}\n" for (qw(fsize mtime version ctime));
140 180
141 print $fh "[HOSTS]\n"; 181 print $fh "[HOSTS]\n";
142 while (my ($k,$v) = each %{$self->{host}}) { 182 while (my ($k,$v) = each %{$self->{host}}) {
143 print $fh "$k=$v\n"; 183 print $fh "$k=$v\n";
144 } 184 }
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 flushfh($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 flushfh($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;
239 my ($add, $del) = gendiff $self->{idx}, \@idx; 306 $self->{ctime} = time;
240 push @{$self->{diff}}, [
241 $mtime,
242 $add, $del,
243 ] if @$add || @$del;
244 307
308 $self->gendiff($self->{idx}, \@idx);
309
245 $self->{fsize} = $fsize; 310 $self->{fsize} = $fsize;
246 $self->{mtime} = $mtime; 311 $self->{mtime} = $mtime;
247 $self->{idx} = \@idx; 312 $self->{idx} = \@idx;
248 313
249 $self->dirty; 314 $self->dirty;
315 $self->write_mdif;#d#
316}
250 317
251 return 2; 318sub inventory {
252 } else { 319 hash sort map { $_->[1] } @{$_[0]{idx}};
253 slog 2, "$path: no longer exists\n"; 320}
254 unlink $conf;
255 321
256 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;
257 } 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 seek $self->{fh}, $msg->[0], SEEK_SET
356 or die "$self->{path}: $!";
357
358 $msg->[1] == read $self->{fh}, $mail, $msg->[1]
359 or die "$self->{path}: $!";
360
361 $mail =~ /^From \S/
362 or die "$self->{path}: mail folder corrupted";
363
364 $mail;
365}
366
367# begin updating folder
368sub begin_update {
369 my $self = shift;
370
371 $self->{oidx} = $self->{idx};
372}
373
374sub delete {
375 my $self = shift;
376 my $temp = "$self->{path}~";
377
378 $self->iidx unless $self->{iidx};
379
380 for (@_) {
381 if (exists $self->{iidx}{$_}) { # at least one message exists
382 my $guard = $::lockdisk->guard;
383 my %del; @del{@_} = ();
384 my @nidx;
385
386 open my $fh, ">", $temp
387 or die "$temp: $!";
388
389 eval {
390 0 == setlkw(fileno $fh, 2)
391 or die "$self->{path}~: $!";
392
393 $self->{fsize} = 0; # we virtually truncated the file
394
395 my $dofs = 0;
396 for (@{delete $self->{idx}}) {
397 my $hash = $_->[1];
398 my $buf;
399
400 unless (exists $del{$hash}) {
401 my ($ofs, $len) = @{$self->{iidx}{$hash}};
402
403 $len or die;
404
405 seek $self->{fh}, $ofs, SEEK_SET
406 or die "$self->{path}: $!";
407
408 $len == read $self->{fh}, $buf, $len
409 or die "$self->{path}: $!";
410
411 $buf =~ /^From \S/
412 or die "$self->{path}: corrupted mail folder";
413
414 print $fh $buf
415 or die "$self->{path}: $!";
416
417 push @nidx, [$dofs, $hash];
418 $self->{iidx}{$hash}[0] = $dofs;
419 $dofs += $len;
420
421 &::give unless ++$ecnt & 255;
422 } else {
423 delete $self->{iidx}{$hash};
424 slog 0, "skipping/deleting $hash\n";
425 }
426 }
427 };
428
429 if ($@) {
430 close $fh;
431 unlink $temp;
432 die;
433 }
434
435 File::Sync::fsync($fh);
436 replace $temp, $self->{path};
437
438 $self->{fh} = $fh;
439 $self->{rw} = 1;
440
441 delete $self->{iidx};
442
443 $self->{idx} = \@nidx;
444 $self->{fsize} = $ofs;
445
446 return;
447 }
448 }
449}
450
451sub append {
452 my ($self, $hash, $mail) = @_;
453
454 if (length $mail) {
455 $self->open(1);
456
457 seek $self->{fh}, $self->{fsize}, SEEK_SET
458 or die "$self->{path}: $!";
459
460 print {$self->{fh}} $mail
461 or die "$self->{path}: $!";
462
463 push @{$self->{idx}}, [$self->{fsize}, $hash];
464 $self->{fsize} += length $mail;
465 }
466}
467
468sub end_update {
469 my $self = shift;
470
471 $self->gendiff((delete $self->{oidx}, $self->{idx}));
472
473 flushfh($self->{fh});
474 File::Sync::fsync($self->{fh});
475
476 stat $self->{fh}
477 or die "$self->{path}: $!";
478
479 $self->{fsize} = (stat _)[7];
480 $self->{mtime} = (stat _)[9];
481
482 $self->dirty;
258} 483}
259 484
2601; 4851;
261 486
487__DATA__
488__C__
489#include <unistd.h>
490#include <fcntl.h>
491
492/* mode0 unlock, mode1 rlock, mode2 rwlock */
493int setlkw(int fd, int mode)
494{
495 struct flock l;
496
497 l.l_type = mode == 0 ? F_UNLCK
498 : mode == 1 ? F_RDLCK
499 : F_WRLCK;
500 l.l_whence = SEEK_SET;
501 l.l_start = 0;
502 l.l_len = 0;
503
504 return fcntl (fd, F_SETLKW, &l);
505}
506

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines