# Copyright (C) all contributors # License: AGPL-3.0+ # # 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;