]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/LeiExportKw.pm
lei: use RENAME_NOREPLACE on Linux 3.15+
[public-inbox.git] / lib / PublicInbox / LeiExportKw.pm
index 42a5ff2269d0a16a2875ccc0c9188c08ea4d4b8f..ceeef7f21d54e41ab90a868ed5f1b93ffbea4b7d 100644 (file)
@@ -7,6 +7,7 @@ use strict;
 use v5.10.1;
 use parent qw(PublicInbox::IPC PublicInbox::LeiInput);
 use Errno qw(EEXIST ENOENT);
+use PublicInbox::Syscall qw(rename_noreplace);
 
 sub export_kw_md { # LeiMailSync->each_src callback
        my ($oidbin, $id, $self, $mdir) = @_;
@@ -25,39 +26,29 @@ sub export_kw_md { # LeiMailSync->each_src callback
        }
        $bn .= ':2,'.
                PublicInbox::LeiToMail::kw2suffix([keys %$sto_kw], @$unknown);
+       return if $bn eq $$id;
        my $dst = "$mdir/cur/$bn";
-       my @fail;
        my $lei = $self->{lei};
        for my $d (@try) {
                my $src = "$mdir/$d/$$id";
-               next if $src eq $dst;
-
-               # we use link(2) + unlink(2) since rename(2) may
-               # inadvertently clobber if the "uniquefilename" part wasn't
-               # actually unique.
-               if (link($src, $dst)) { # success
-                       # unlink(2) may ENOENT from parallel invocation,
-                       # ignore it, but not other serious errors
-                       if (!unlink($src) and $! != ENOENT) {
-                               $lei->child_error(1, "E: unlink($src): $!");
-                       }
-                       $lei->{sto}->ipc_do('lms_mv_src', "maildir:$mdir",
+               if (rename_noreplace($src, $dst)) { # success
+                       $self->{lms}->mv_src("maildir:$mdir",
                                                $oidbin, $id, $bn);
-                       return; # success anyways if link(2) worked
-               }
-               if ($! == ENOENT && !-e $src) { # some other process moved it
-                       $lei->{sto}->ipc_do('lms_clear_src',
-                                               "maildir:$mdir", $id);
-                       next;
-               }
-               push @fail, $src if $! != EEXIST;
+                       return; # success
+               } elsif ($! == EEXIST) { # lost race with lei/store?
+                       return;
+               } elsif ($! != ENOENT) {
+                       $lei->child_error(1,
+                               "E: rename_noreplace($src -> $dst): $!");
+               } # else loop @try
        }
-       return unless @fail;
-       # both tries failed
        my $e = $!;
-       my $orig = '['.join('|', @fail).']';
+       # both tries failed
        my $oidhex = unpack('H*', $oidbin);
-       $lei->child_error(1, "link($orig, $dst) ($oidhex): $e");
+       my $src = "$mdir/{".join(',', @try)."}/$$id";
+       $lei->child_error(1, "rename_noreplace($src -> $dst) ($oidhex): $e");
+       for (@try) { return if -e "$mdir/$_/$$id" }
+       $self->{lms}->clear_src("maildir:$mdir", $id);
 }
 
 sub export_kw_imap { # LeiMailSync->each_src callback
@@ -69,18 +60,17 @@ sub export_kw_imap { # LeiMailSync->each_src callback
 # overrides PublicInbox::LeiInput::input_path_url
 sub input_path_url {
        my ($self, $input, @args) = @_;
-       my $lms = $self->{-lms_ro} //= $self->{lse}->lms;
+       $self->{lms}->lms_write_prepare;
        if ($input =~ /\Amaildir:(.+)/i) {
                my $mdir = $1;
                require PublicInbox::LeiToMail; # kw2suffix
-               $lms->each_src($input, \&export_kw_md, $self, $mdir);
+               $self->{lms}->each_src($input, \&export_kw_md, $self, $mdir);
        } elsif ($input =~ m!\Aimaps?://!i) {
                my $uri = PublicInbox::URIimap->new($input);
                my $mic = $self->{nwr}->mic_for_folder($uri);
-               $lms->each_src($$uri, \&export_kw_imap, $self, $mic);
+               $self->{lms}->each_src($$uri, \&export_kw_imap, $self, $mic);
                $mic->expunge;
        } else { die "BUG: $input not supported" }
-       my $wait = $self->{lei}->{sto}->ipc_do('done');
 }
 
 sub lei_export_kw {
@@ -88,26 +78,23 @@ sub lei_export_kw {
        my $sto = $lei->_lei_store or return $lei->fail(<<EOM);
 lei/store uninitialized, see lei-import(1)
 EOM
-       my $lse = $sto->search;
-       my $lms = $lse->lms or return $lei->fail(<<EOM);
+       my $lms = $lei->lms or return $lei->fail(<<EOM);
 lei mail_sync uninitialized, see lei-import(1)
 EOM
-       my $opt = $lei->{opt};
-       if (defined(my $all = $opt->{all})) { # --all=<local|remote>
+       if (defined(my $all = $lei->{opt}->{all})) { # --all=<local|remote>
                $lms->group2folders($lei, $all, \@folders) or return;
+               @folders = grep(/\A(?:maildir|imaps?):/i, @folders);
        } else {
-               my $err = $lms->arg2folder($lei, \@folders);
-               $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
-               return $lei->fail($err->{fail}) if $err->{fail};
+               $lms->arg2folder($lei, \@folders); # may die
        }
-       my $self = bless { lse => $lse }, __PACKAGE__;
+       $lms->lms_pause;
+       my $self = bless { lse => $sto->search, lms => $lms }, __PACKAGE__;
        $lei->{opt}->{'mail-sync'} = 1; # for prepare_inputs
        $self->prepare_inputs($lei, \@folders) or return;
-       my $j = $opt->{jobs} // scalar(@{$self->{inputs}}) || 1;
        if (my @ro = grep(!/\A(?:maildir|imaps?):/i, @folders)) {
                return $lei->fail("cannot export to read-only folders: @ro");
        }
-       my $m = $opt->{mode} // 'merge';
+       my $m = $lei->{opt}->{mode} // 'merge';
        if ($m eq 'merge') { # default
                $self->{-merge_kw} = 1;
        } elsif ($m eq 'set') {
@@ -122,12 +109,9 @@ EOM
                $self->{imap_mod_kw} = $net->can($self->{-merge_kw} ?
                                        'imap_add_kw' : 'imap_set_kw');
        }
-       undef $lms; # for fork
        my $ops = {};
-       $sto->write_prepare($lei);
        $lei->{auth}->op_merge($ops, $self) if $lei->{auth};
-       $self->{-wq_nr_workers} = $j // 1; # locked
-       (my $op_c, $ops) = $lei->workers_start($self, $j, $ops);
+       (my $op_c, $ops) = $lei->workers_start($self, 1, $ops);
        $lei->{wq1} = $self;
        $lei->{-err_type} = 'non-fatal';
        net_merge_all_done($self) unless $lei->{auth};
@@ -136,10 +120,13 @@ EOM
 
 sub _complete_export_kw {
        my ($lei, @argv) = @_;
-       my $sto = $lei->_lei_store or return;
-       my $lms = $sto->search->lms or return;
+       my $lms = $lei->lms or return ();
        my $match_cb = $lei->complete_url_prepare(\@argv);
-       map { $match_cb->($_) } $lms->folders;
+       # filter-out read-only sources:
+       my @k = grep(!m!(?://;AUTH=ANONYMOUS\@|\A(?:nntps?|s?news)://)!,
+                       $lms->folders($argv[-1] // undef, 1));
+       my @m = map { $match_cb->($_) } @k;
+       @m ? @m : @k;
 }
 
 no warnings 'once';
@@ -147,7 +134,4 @@ no warnings 'once';
 *ipc_atfork_child = \&PublicInbox::LeiInput::input_only_atfork_child;
 *net_merge_all_done = \&PublicInbox::LeiInput::input_only_net_merge_all_done;
 
-# the following works even when LeiAuth is lazy-loaded
-*net_merge_all = \&PublicInbox::LeiAuth::net_merge_all;
-
 1;