1 # Copyright (C) 2016-2021 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 # Emergency Maildir delivery for MDA
5 package PublicInbox::Emergency;
8 use Fcntl qw(:DEFAULT SEEK_SET);
9 use Sys::Hostname qw(hostname);
10 use IO::Handle; # ->flush
14 my ($class, $dir) = @_;
16 foreach (qw(new tmp cur)) {
20 if (!File::Path::mkpath($d) && !-d $d) {
21 die "failed to mkpath($d): $!\n";
24 bless { dir => $dir, t => 0 }, $class;
28 my ($self, $pid, $dir) = @_;
29 my $host = $self->{short_host} //= (split(/\./, hostname))[0];
32 if ($self->{t} != $now) {
34 $n = $self->{cnt} = 0;
38 "$self->{dir}/$dir/$self->{t}.$pid"."_$n.$host";
42 my ($self, $strref) = @_;
44 my $tmp_key = "tmp.$pid";
45 die "already in transaction: $self->{$tmp_key}" if $self->{$tmp_key};
48 $tmp = _fn_in($self, $pid, 'tmp');
50 } while (!sysopen($fh, $tmp, O_CREAT|O_EXCL|O_RDWR) and $! == EEXIST);
51 print $fh $$strref or die "write failed: $!";
52 $fh->flush or die "flush failed: $!";
54 $self->{$tmp_key} = $tmp;
60 my $tmp = delete $self->{"tmp.$$"} or return;
61 unlink($tmp) or warn "Failed to unlink $tmp: $!";
67 my $fh = $self->{fh} or die "{fh} not open!\n";
68 seek($fh, 0, SEEK_SET) or die "seek(fh) failed: $!";
69 sysseek($fh, 0, SEEK_SET) or die "sysseek(fh) failed: $!";
76 my $tmp = delete $self->{"tmp.$pid"} or return;
80 $new = _fn_in($self, $pid, 'new');
81 } while (!($ok = link($tmp, $new)) && $! == EEXIST);
82 die "link($tmp, $new): $!" unless $ok;
83 unlink($tmp) or warn "Failed to unlink $tmp: $!";
86 sub DESTROY { commit($_[0]) }