--- syncmail/folder.pm 2001/10/28 04:00:58 1.3 +++ syncmail/folder.pm 2001/10/28 20:52:24 1.4 @@ -2,17 +2,61 @@ BEGIN { *slog = \&::slog }; -use Digest::SHA1; +use Fcntl; +use File::Sync (); + +use Inline Config => NAME => "syncmail::folder"; +use Inline C; use constant MDIFVERSION => 1; +BEGIN { + if (1) { + use OpenSSL (); + *hash = \&OpenSSL::Digest::sha1_hex; + } elsif (0) { + # use Digest::SHA1 (); + my $digest = new Digest::SHA1; + *hash = sub { + $digest->reset; + $digest->add(@_); + $mid = $digest->hexdigest; + }; + } +} + +sub flushfh { + my $oldfh = select $_[0]; + $| = 1; + select $oldfh; +} + +# rename a file and fsync the directory +sub replace { + my ($src, $dst) = @_; + my $self = shift; + + rename $src, $dst; + + $dst =~ s/[^\/]*$/./; + + # now sync the directory + + open my $dir, "<", $dst + or die "$dst: $!"; + + File::Sync::fsync($dir); +} + sub new { my $class = shift; my %arg = @_; - bless { + my $self = bless { path => "$::PREFIX/$arg{name}", %arg, }, $class; + $self->open(0); + $self; } sub dirty { @@ -28,19 +72,15 @@ # $header includes the mbox From_ line without # the leading From_ itself. sub parse_mbox { - my ($path, $cb) = @_; - - open my $fh, "<", $path - or die "$path: $!"; + my ($fh, $cb) = @_; local $/ = "\n\n"; my ($head, $body, $offs); - 5 == read $fh, $head, 5 - or return; + read $fh, $head, 5; - $head eq "From " + $head eq "From " or $head eq "" or return; $offs = 0; @@ -59,7 +99,7 @@ chomp $body; $cb->($offs, \$head, \$body); $offs = (tell $fh) - 5; - ::give unless ++$ecnt & 1023; + &::give unless ++$ecnt & 255; } 1; @@ -152,29 +192,18 @@ print $fh "-$_\n" for @{$_->[2]}; } + flushfh $fh; + File::Sync::fsync($fh); close $fh or die "$path~: unable to create updated .mdif: $!"; - rename "$path~", $path; + replace("$path~", $path); delete $self->{dirty}; } -if (1) { - use OpenSSL (); - *hash = \&OpenSSL::Digest::sha1_hex; -} elsif (0) { - # use Digest::SHA1; - my $digest = new Digest::SHA1; - *hash = sub { - $digest->reset; - $digest->add(@_); - $mid = $digest->hexdigest; - }; -} - sub gendiff { - my ($d1, $d2) = @_; + my ($self, $d1, $d2) = @_; my (@add, @del); my (%d1, %d2); @@ -195,7 +224,10 @@ push @add, $_->[1] unless exists $d1{$_->[1]}; } - (\@add, \@del); + push @{$self->{diff}}, [ + $self->{ctime}, + \@add, \@del, + ] if @add || @del; } sub check { @@ -206,57 +238,164 @@ slog 3, "checking $path\n"; - if (stat $path) { - my ($fsize, $mtime) = (stat _)[7, 9]; + stat $path + or die "$path: $!"; - if (open my $fh, "<", $conf) { - my %conf; - <$fh>; # skip initial comment - <$fh> eq "[SYNCMAIL]\n" - or die "$conf: format error"; - while (<$fh> =~ /^([a-z]+)\s*=\s*(.*)$/) { - $conf{$1} = $2; - } - return 1 if $fsize == $conf{fsize} - && $mtime == $conf{mtime}; + my ($fsize, $mtime) = (stat _)[7, 9]; - $conf{mtime} <= $mtime - or die "$path: folder older than mdif"; + if (open my $fh, "<", $conf) { + my %conf; + <$fh>; # skip initial comment + <$fh> eq "[SYNCMAIL]\n" + or die "$conf: format error"; + while (<$fh> =~ /^([a-z]+)\s*=\s*(.*)$/) { + $conf{$1} = $2; } + return 1 if $fsize == $conf{fsize} + && $mtime == $conf{mtime}; + + $conf{mtime} <= $mtime + or die "$path: folder older than mdif"; + } + + slog 2, "updating $path\n"; - slog 2, "updating $path\n"; + my @idx; - my @idx; + parse_mbox $self->{fh}, sub { + my ($offs, $head, $body) = @_; + push @idx, [$offs, hash($$head, "\0", $$body)]; + } or die "$path: no valid mbox file"; - parse_mbox $path, sub { - my ($offs, $head, $body) = @_; - push @idx, [$offs, hash($$head, "\0", $$body)]; - } or return (); + $self->read_mdif; - $self->read_mdif; + $self->{version} ||= MDIFVERSION; + $self->{ctime} = time; - $self->{version} ||= MDIFVERSION; - $self->{fsize} = $fsize; - $self->{mtime} = $mtime; - $self->{ctime} = time; - $self->{idx} = \@idx; + $self->gendiff($self->{idx}, \@idx); - my ($add, $del) = gendiff $self->{idx}, \@idx; - push @{$self->{diff}}, [ - $self->{ctime}, - $add, $del, - ] if @$add || @$del; + $self->{fsize} = $fsize; + $self->{mtime} = $mtime; + $self->{idx} = \@idx; + + $self->dirty; + $self->write_mdif; +} + +sub inventory { + hash sort map { $_->[1] } @{$_[0]{idx}}; +} - $self->dirty; +sub open { + my ($self, $rw) = @_; - return 2; - } else { - slog 2, "$path: no longer exists\n"; - unlink $conf; + if (!$self->{fh} || $self->{rw} != $rw) { + $self->close; + $self->{rw} = $rw; + sysopen $self->{fh}, $self->{path}, + O_CREAT | ($rw ? O_RDWR : O_RDONLY), + 0666 + or die "$self->{path}: $!"; + 0 == setlkw(fileno $self->{fh}, $rw ? 2 : 1) + or die "$self->{path}: $!"; - return (); } } +sub close { + my $self = shift; + + flushfh $self->{fh}; + File::Sync::fsync($self->{fh}); + delete $self->{fh}; +} + +# begin updating folder +sub begin_update { + my $self = shift; + + $self->{oidx} = $self->{idx}; + +} + +sub delete { + my $self = shift; + my $temp = "$self->{path}~"; + + if (@_) { + my $guard = $::lockdisk->guard; + my %del; @del{@_} = (); + + open my $fh, ">", $temp + or die "$temp: $!"; + + my $nidx; + my $idx = delete $self->{idx}; + push @$idx, [$self->{fsize}]; + $self->{fsize} = 0; # we virtually truncated the file + + slog 0, "XXXXXXXXXXXXXXX @_\n";#d# + + my $ofs = 0; + for (0 .. @$idx - 2) { + my $buf; + + unless (exists $del{$idx->[$_][1]}) { + my $len = $idx->[$_+1][0] - $idx->[$_][0]; + + slog 0, "$idx->[$_][1] $idx->[$_+1][0] - $idx->[$_][0]\n";#d# + + seek $self->{fh}, $idx->[$_][0],SEEK_SET + or die "$self->{path}: $!"; + + $len == read $self->{fh}, $buf, $len + or die "$self->{path}: $!"; + + $buf =~ /^From \S/ + or die "$self->{path}: corrupted mail folder"; + + &::give unless ++$ecnt & 255; + } else { + slog 0, "skipping $idx->[$_][1]\n"; + } + } + + File::Sync::fsync($fh); + close $fh; + + # replace $temp, $self->{path} + } +} + +sub end_update { +} + +sub append { + my $self = shift; + + &update; + #$self->open(1); +} + 1; +__DATA__ +__C__ +#include +#include + +/* mode0 unlock, mode1 rlock, mode2 rwlock */ +int setlkw(int fd, int mode) +{ + struct flock l; + + l.l_type = mode == 0 ? F_UNLCK + : mode == 1 ? F_RDLCK + : F_WRLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + + return fcntl (fd, F_SETLKW, &l); +} +