t/purge.t
t/qspawn.t
t/reindex-time-range.t
+t/rename_noreplace.t
t/replace.t
t/reply.t
t/run.perl
-# Copyright 2021 all contributors <meta@public-inbox.org>
+# Copyright all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <http://www.gnu.org/licenses/agpl-3.0.txt>
# Dump syscall numbers under Linux and any other kernel which
# promises stable syscall numbers. This is to maintain
eval 'exec perl -S $0 ${1+"$@"}' # no shebang
if 0; # running under some shell
use strict;
+use v5.10.1;
use File::Temp 0.19;
+use POSIX qw(uname);
+say '$machine='.(POSIX::uname())[-1];
my $cc = $ENV{CC} // 'cc';
my @cflags = split(/\s+/, $ENV{CFLAGS} // '-Wall');
my $str = do { local $/; <DATA> };
D(SYS_inotify_add_watch);
D(SYS_inotify_rm_watch);
D(SYS_prctl);
+#ifdef SYS_renameat2
+ D(SYS_renameat2);
+#endif
#endif /* Linux, any other OSes with stable syscalls? */
printf("size_t=%zu off_t=%zu\n", sizeof(size_t), sizeof(off_t));
return 0;
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) = @_;
my $lei = $self->{lei};
for my $d (@try) {
my $src = "$mdir/$d/$$id";
-
- # 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): $!");
- }
+ if (rename_noreplace($src, $dst)) { # success
$self->{lms}->mv_src("maildir:$mdir",
$oidbin, $id, $bn);
- return; # success anyways if link(2) worked
+ return; # success
} elsif ($! == EEXIST) { # lost race with lei/store?
return;
} elsif ($! != ENOENT) {
- $lei->child_error(1, "E: link($src -> $dst): $!");
+ $lei->child_error(1,
+ "E: rename_noreplace($src -> $dst): $!");
} # else loop @try
}
my $e = $!;
# both tries failed
my $oidhex = unpack('H*', $oidbin);
my $src = "$mdir/{".join(',', @try)."}/$$id";
- $lei->child_error(1, "link($src -> $dst) ($oidhex): $e");
+ $lei->child_error(1, "rename_noreplace($src -> $dst) ($oidhex): $e");
for (@try) { return if -e "$mdir/$_/$$id" }
$self->{lms}->clear_src("maildir:$mdir", $id);
}
use IO::Handle (); # ->autoflush
use Sys::Syslog qw(syslog openlog);
use Errno qw(EEXIST ENOENT);
+use PublicInbox::Syscall qw(rename_noreplace);
sub new {
my (undef, $dir, $opt) = @_;
my $dst = "$mdir/cur/$bn";
for my $d (@try) {
my $src = "$mdir/$d/$orig";
- if (link($src, $dst)) {
- if (!unlink($src) and $! != ENOENT) {
- syslog('warning', "unlink($src): $!");
- }
+ if (rename_noreplace($src, $dst)) {
# TODO: verify oidbin?
$self->{lms}->mv_src("maildir:$mdir",
$oidbin, \$orig, $bn);
} elsif ($! == EEXIST) { # lost race with "lei export-kw"?
return;
} elsif ($! != ENOENT) {
- syslog('warning', "link($src -> $dst): $!");
+ syslog('warning', "rename_noreplace($src -> $dst): $!");
}
}
for (@try) { return if -e "$mdir/$_/$orig" };
use Symbol qw(gensym);
use IO::Handle; # ->autoflush
use Fcntl qw(SEEK_SET SEEK_END O_CREAT O_EXCL O_WRONLY);
+use PublicInbox::Syscall qw(rename_noreplace);
my %kw2char = ( # Maildir characters
draft => 'D',
$rand = '';
do {
$base = $rand.$common.':2,'.kw2suffix($kw);
- } while (!($ok = link($tmp, $dst.$base)) && $!{EEXIST} &&
- ($rand = _rand.','));
- die "link($tmp, $dst$base): $!" unless $ok;
- unlink($tmp) or warn "W: failed to unlink $tmp: $!\n";
+ } while (!($ok = rename_noreplace($tmp, $dst.$base)) &&
+ $!{EEXIST} && ($rand = _rand.','));
\$base;
} else {
my $err = "Error writing $smsg->{blob} to $dst: $!\n";
# License or the Artistic License, as specified in the Perl README file.
package PublicInbox::Syscall;
use strict;
+use v5.10.1;
use parent qw(Exporter);
-use POSIX qw(ENOSYS O_NONBLOCK);
+use POSIX qw(ENOENT EEXIST ENOSYS O_NONBLOCK);
use Config;
# $VERSION = '0.25'; # Sys::Syscall version
EPOLLIN EPOLLOUT EPOLLET
EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD
EPOLLONESHOT EPOLLEXCLUSIVE
- signalfd);
+ signalfd rename_noreplace);
our %EXPORT_TAGS = (epoll => [qw(epoll_ctl epoll_create epoll_wait
EPOLLIN EPOLLOUT
EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD
$SYS_epoll_ctl,
$SYS_epoll_wait,
$SYS_signalfd4,
+ $SYS_renameat2,
);
my $SFD_CLOEXEC = 02000000; # Perl does not expose O_CLOEXEC
our $no_deprecated = 0;
if ($^O eq "linux") {
- my $machine = (POSIX::uname())[-1];
+ my (undef, undef, $release, undef, $machine) = POSIX::uname();
+ my ($maj, $min) = ($release =~ /\A([0-9]+)\.([0-9]+)/);
+ $SYS_renameat2 = 0 if "$maj.$min" < 3.15;
# whether the machine requires 64-bit numbers to be on 8-byte
# boundaries.
my $u64_mod_8 = 0;
$SYS_epoll_ctl = 255;
$SYS_epoll_wait = 256;
$SYS_signalfd4 = 327;
+ $SYS_renameat2 //= 353;
} elsif ($machine eq "x86_64") {
$SYS_epoll_create = 213;
$SYS_epoll_ctl = 233;
$SYS_epoll_wait = 232;
$SYS_signalfd4 = 289;
+ $SYS_renameat2 //= 316;
} elsif ($machine eq 'x32') {
$SYS_epoll_create = 1073742037;
$SYS_epoll_ctl = 1073742057;
$SYS_epoll_wait = 1073742056;
$SYS_signalfd4 = 1073742113;
+ $SYS_renameat2 //= 0x40000000 + 316;
} elsif ($machine eq 'sparc64') {
$SYS_epoll_create = 193;
$SYS_epoll_ctl = 194;
$SYS_epoll_wait = 195;
$u64_mod_8 = 1;
$SYS_signalfd4 = 317;
+ $SYS_renameat2 //= 345;
$SFD_CLOEXEC = 020000000;
} elsif ($machine =~ m/^parisc/) {
$SYS_epoll_create = 224;
$SYS_epoll_wait = 238;
$u64_mod_8 = 1;
$SYS_signalfd4 = 313;
+ $SYS_renameat2 //= 357;
} elsif ($machine eq "ppc") {
$SYS_epoll_create = 236;
$SYS_epoll_ctl = 237;
$SYS_epoll_wait = 238;
$u64_mod_8 = 1;
$SYS_signalfd4 = 313;
+ $SYS_renameat2 //= 357;
} elsif ($machine =~ m/^s390/) {
$SYS_epoll_create = 249;
$SYS_epoll_ctl = 250;
$SYS_epoll_wait = 251;
$u64_mod_8 = 1;
$SYS_signalfd4 = 322;
+ $SYS_renameat2 //= 347;
} elsif ($machine eq "ia64") {
$SYS_epoll_create = 1243;
$SYS_epoll_ctl = 1244;
$u64_mod_8 = 1;
$no_deprecated = 1;
$SYS_signalfd4 = 74;
+ $SYS_renameat2 //= 276;
} elsif ($machine =~ m/arm(v\d+)?.*l/) {
# ARM OABI
$SYS_epoll_create = 250;
$SYS_epoll_wait = 252;
$u64_mod_8 = 1;
$SYS_signalfd4 = 355;
+ $SYS_renameat2 //= 382;
} elsif ($machine =~ m/^mips64/) {
$SYS_epoll_create = 5207;
$SYS_epoll_ctl = 5208;
$SYS_epoll_wait = 5209;
$u64_mod_8 = 1;
$SYS_signalfd4 = 5283;
+ $SYS_renameat2 //= 5311;
} elsif ($machine =~ m/^mips/) {
$SYS_epoll_create = 4248;
$SYS_epoll_ctl = 4249;
$SYS_epoll_wait = 4250;
$u64_mod_8 = 1;
$SYS_signalfd4 = 4324;
+ $SYS_renameat2 //= 4351;
} else {
# as a last resort, try using the *.ph files which may not
# exist or may be wrong
}
}
+sub _rename_noreplace_racy ($$) {
+ my ($old, $new) = @_;
+ if (link($old, $new)) {
+ warn "unlink $old: $!\n" if !unlink($old) && $! != ENOENT;
+ 1
+ } else {
+ undef;
+ }
+}
+
+# TODO: support FD args?
+sub rename_noreplace ($$) {
+ my ($old, $new) = @_;
+ if ($SYS_renameat2) { # RENAME_NOREPLACE = 1, AT_FDCWD = -100
+ my $ret = syscall($SYS_renameat2, -100, $old, -100, $new, 1);
+ if ($ret == 0) {
+ 1; # like rename() perlop
+ } elsif ($! == ENOSYS) {
+ undef $SYS_renameat2;
+ _rename_noreplace_racy($old, $new);
+ } else {
+ undef
+ }
+ } else {
+ _rename_noreplace_racy($old, $new);
+ }
+}
+
1;
=head1 WARRANTY
--- /dev/null
+#!perl -w
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use strict;
+use v5.10.1;
+use PublicInbox::TestCommon;
+use_ok 'PublicInbox::Syscall', 'rename_noreplace';
+my ($tmpdir, $for_destroy) = tmpdir;
+
+open my $fh, '>', "$tmpdir/a" or xbail $!;
+my @sa = stat($fh);
+is(rename_noreplace("$tmpdir/a", "$tmpdir/b"), 1, 'rename_noreplace');
+my @sb = stat("$tmpdir/b");
+ok(scalar(@sb), 'new file exists');
+ok(!-e "$tmpdir/a", 'original gone');
+is("@sa[0,1]", "@sb[0,1]", 'same st_dev + st_ino');
+
+is(rename_noreplace("$tmpdir/a", "$tmpdir/c"), undef, 'undef on ENOENT');
+ok($!{ENOENT}, 'ENOENT set when missing');
+
+open $fh, '>', "$tmpdir/a" or xbail $!;
+is(rename_noreplace("$tmpdir/a", "$tmpdir/b"), undef, 'undef on EEXIST');
+ok($!{EEXIST}, 'EEXIST set when missing');
+is_deeply([stat("$tmpdir/b")], \@sb, 'target unchanged on EEXIST');
+
+done_testing;