# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
-# represents an POP3D (currently a singleton)
+# represents an POP3D
package PublicInbox::POP3D;
use v5.12;
use parent qw(PublicInbox::Lock);
use PublicInbox::POP3;
use PublicInbox::Syscall;
use File::Temp 0.19 (); # 0.19 for ->newdir
-use File::FcntlLock;
use Fcntl qw(F_SETLK F_UNLCK F_WRLCK SEEK_SET);
+my @FLOCK;
+if ($^O eq 'linux' || $^O =~ /bsd/) {
+ require Config;
+ my $off_t;
+ my $sz = $Config::Config{lseeksize};
+
+ if ($sz == 8 && eval('length(pack("q", 1)) == 8')) { $off_t = 'q' }
+ elsif ($sz == 4) { $off_t = 'l' }
+ else { warn "sizeof(off_t)=$sz requires File::FcntlLock\n" }
+
+ if (defined($off_t)) {
+ if ($^O eq 'linux') {
+ @FLOCK = ("ss\@8$off_t$off_t\@32",
+ qw(l_type l_whence l_start l_len));
+ } elsif ($^O =~ /bsd/) {
+ @FLOCK = ("${off_t}${off_t}lss\@256",
+ qw(l_start l_len l_pid l_type l_whence));
+ }
+ }
+}
+@FLOCK or eval { require File::FcntlLock } or
+ die "File::FcntlLock required for POP3 on $^O: $@\n";
sub new {
- my ($cls, $pi_cfg) = @_;
- $pi_cfg //= PublicInbox::Config->new;
- my $d = $pi_cfg->{'publicinbox.pop3state'} //
- die "publicinbox.pop3state undefined\n";
- -d $d or do {
- require File::Path;
- File::Path::make_path($d, { mode => 0700 });
- PublicInbox::Syscall::nodatacow_dir($d);
- };
+ my ($cls) = @_;
bless {
err => \*STDERR,
out => \*STDOUT,
- pi_cfg => $pi_cfg,
- lock_path => "$d/db.lock", # PublicInbox::Lock to protect SQLite
+ # pi_cfg => PublicInbox::Config
+ # lock_path => ...
# interprocess lock is the $pop3state/txn.locks file
# txn_locks => {}, # intraworker locks
- # accept_tls => { SSL_server => 1, ..., SSL_reuse_ctx => ... }
+ # ssl_ctx_opt => { SSL_cert_file => ..., SSL_key_file => ... }
}, $cls;
}
my ($self, $sig) = @_;
# TODO share pi_cfg with nntpd/imapd inside -netd
my $new = PublicInbox::Config->new;
- my $old = $self->{pi_cfg};
- my $s = 'publicinbox.pop3state';
- $new->{$s} //= $old->{$s};
- if ($new->{$s} ne $old->{$s}) {
- warn <<EOM;
+ my $d = $new->{'publicinbox.pop3state'} //
+ die "publicinbox.pop3state undefined ($new->{-f})\n";
+ -d $d or do {
+ require File::Path;
+ File::Path::make_path($d, { mode => 0700 });
+ PublicInbox::Syscall::nodatacow_dir($d);
+ };
+ $self->{lock_path} //= "$d/db.lock";
+ if (my $old = $self->{pi_cfg}) {
+ my $s = 'publicinbox.pop3state';
+ $new->{$s} //= $old->{$s};
+ return warn <<EOM if $new->{$s} ne $old->{$s};
$s changed: `$old->{$s}' => `$new->{$s}', config reload ignored
EOM
- } else {
- $self->{pi_cfg} = $new;
}
+ $self->{pi_cfg} = $new;
}
# persistent tables
$dbh;
}
+sub _setlk ($%) {
+ my ($self, %lk) = @_;
+ $lk{l_pid} = 0; # needed for *BSD
+ $lk{l_whence} = SEEK_SET;
+ if (@FLOCK) {
+ fcntl($self->{txn_fh}, F_SETLK,
+ pack($FLOCK[0], @lk{@FLOCK[1..$#FLOCK]}));
+ } else {
+ my $fs = File::FcntlLock->new(%lk);
+ $fs->lock($self->{txn_fh}, F_SETLK);
+ }
+}
+
sub lock_mailbox {
my ($self, $pop3) = @_; # pop3 - PublicInbox::POP3 client object
my $lk = $self->lock_for_scope; # lock the SQLite DB, only
return if $self->{txn_locks}->{$txn_id};
# see if it's locked by another worker:
- my $fs = File::FcntlLock->new;
- $fs->l_type(F_WRLCK);
- $fs->l_whence(SEEK_SET);
- $fs->l_start($txn_id - 1);
- $fs->l_len(1);
- $fs->lock($self->{txn_fh}, F_SETLK) or return;
+ _setlk($self, l_type => F_WRLCK, l_start => $txn_id - 1, l_len => 1)
+ or return;
$pop3->{user_id} = $user_id;
$pop3->{txn_id} = $txn_id;
sub unlock_mailbox {
my ($self, $pop3) = @_;
my $txn_id = delete($pop3->{txn_id}) // return;
+ if (!$pop3->{did_quit}) { # deal with QUIT-less disconnects
+ my $lk = $self->lock_for_scope;
+ $self->{-state_dbh}->begin_work;
+ $pop3->__cleanup_state($txn_id);
+ $self->{-state_dbh}->commit;
+ }
delete $self->{txn_locks}->{$txn_id}; # same worker
# other workers
- my $fs = File::FcntlLock->new;
- $fs->l_type(F_UNLCK);
- $fs->l_whence(SEEK_SET);
- $fs->l_start($txn_id - 1);
- $fs->l_len(1);
- $fs->lock($self->{txn_fh}, F_SETLK) or die "F_UNLCK: $!";
+ _setlk($self, l_type => F_UNLCK, l_start => $txn_id - 1, l_len => 1)
+ or die "F_UNLCK: $!";
}
1;