| # Copyright (C) all contributors <meta@public-inbox.org> |
| # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> |
| # |
| # Emergency Maildir delivery for MDA |
| package PublicInbox::Emergency; |
| use v5.12; |
| use Fcntl qw(:DEFAULT SEEK_SET); |
| use Sys::Hostname qw(hostname); |
| use IO::Handle; # ->flush |
| use Errno qw(EEXIST); |
| use File::Path (); |
| |
| sub new { |
| my ($class, $dir) = @_; |
| File::Path::make_path(map { $dir.$_ } qw(/tmp /new /cur)); |
| bless { dir => $dir, t => 0 }, $class; |
| } |
| |
| sub _fn_in { |
| my ($self, $pid, $dir) = @_; |
| my $host = $self->{-host} //= (split(/\./, hostname))[0] // 'localhost'; |
| my $now = time; |
| my $n; |
| if ($self->{t} != $now) { |
| $self->{t} = $now; |
| $n = $self->{cnt} = 0; |
| } else { |
| $n = ++$self->{cnt}; |
| } |
| "$self->{dir}/$dir/$self->{t}.$pid"."_$n.$host"; |
| } |
| |
| sub prepare { |
| my ($self, $strref) = @_; |
| my $pid = $$; |
| my $tmp_key = "tmp.$pid"; |
| die "BUG: in transaction: $self->{$tmp_key}" if $self->{$tmp_key}; |
| my ($tmp, $fh); |
| do { |
| $tmp = _fn_in($self, $pid, 'tmp'); |
| $! = undef; |
| } while (!sysopen($fh, $tmp, O_CREAT|O_EXCL|O_RDWR) and $! == EEXIST); |
| print $fh $$strref or die "print: $!"; |
| $fh->flush or die "flush: $!"; |
| $self->{fh} = $fh; |
| $self->{$tmp_key} = $tmp; |
| } |
| |
| sub abort { |
| my ($self) = @_; |
| delete $self->{fh}; |
| my $tmp = delete $self->{"tmp.$$"} or return; |
| unlink($tmp) or warn "W: unlink($tmp): $!"; |
| undef; |
| } |
| |
| sub fh { |
| my ($self) = @_; |
| my $fh = $self->{fh} or die "BUG: {fh} not open"; |
| seek($fh, 0, SEEK_SET) or die "seek: $!"; |
| sysseek($fh, 0, SEEK_SET) or die "sysseek: $!"; |
| $fh; |
| } |
| |
| sub commit { |
| my ($self) = @_; |
| my $pid = $$; |
| my $tmp = delete $self->{"tmp.$pid"} or return; |
| delete $self->{fh}; |
| my ($new, $ok); |
| do { |
| $new = _fn_in($self, $pid, 'new'); |
| } while (!($ok = link($tmp, $new)) && $! == EEXIST); |
| die "link($tmp, $new): $!" unless $ok; |
| unlink($tmp) or warn "W: unlink($tmp): $!"; |
| } |
| |
| sub DESTROY { commit($_[0]) } |
| |
| 1; |