t/config_limiter.t
t/content_hash.t
t/convert-compact.t
+t/cow.t
t/daemon.t
t/data-gen/.gitignore
t/data/0001.patch
my $self = bless {
xpfx => "$dir/ei".PublicInbox::Search::SCHEMA_VERSION,
topdir => $dir,
+ -opt => $opt,
creat => $opt->{creat},
ibx_map => {}, # (newsgroup//inboxdir) => $ibx
ibx_active => [], # by config section order
PublicInbox::SearchIdx::load_xapian_writable();
my $mi_dir = "$eidx->{xpfx}/misc";
File::Path::mkpath($mi_dir);
- PublicInbox::Syscall::nodatacow_dir($mi_dir);
- my $flags = $PublicInbox::SearchIdx::DB_CREATE_OR_OPEN;
my $opt = $eidx->{-opt};
+ $opt->{cow} or
+ PublicInbox::Syscall::nodatacow_dir($mi_dir);
+ my $flags = $PublicInbox::SearchIdx::DB_CREATE_OR_OPEN;
$flags |= $PublicInbox::SearchIdx::DB_NO_SYNC if !$opt->{fsync};
$flags |= $PublicInbox::SearchIdx::DB_DANGEROUS if $opt->{dangerous};
$json //= PublicInbox::Config::json();
my $opt = $self->{-opt};
if (!-s $f) {
if ($rw) {
- PublicInbox::SQLiteUtil::create_db $f;
+ PublicInbox::SQLiteUtil::create_db $f, $opt;
} else {
$self->{filename} = $f; # die on stat() below:
}
: ($anywhere ? '.*' : '^')."\Q$pfx\E.*";
}
-sub create_db ($) {
- my ($f) = @_;
- require PublicInbox::Syscall;
+sub create_db ($;$) {
+ my ($f, $opt) = @_;
my ($dir) = ($f =~ m!(.+)/[^/]+\z!);
- PublicInbox::Syscall::nodatacow_dir($dir); # for journal/shm/wal
+ unless ($opt->{cow}) {
+ require PublicInbox::Syscall;
+ PublicInbox::Syscall::nodatacow_dir($dir); # for journal/shm/wal
+ }
# SQLite defaults mode to 0644, we want 0666 to respect umask
open my $fh, '+>>', $f;
}
if (!-d $dir && (!$is_shard ||
($is_shard && need_xapian($self)))) {
File::Path::mkpath($dir);
- PublicInbox::Syscall::nodatacow_dir($dir);
+ $self->{-opt}->{cow} or
+ PublicInbox::Syscall::nodatacow_dir($dir);
# owner == self for CodeSearchIdx
$self->{-set_has_threadid_once} = 1 if $owner != $self;
$flag |= $DB_DANGEROUS if $self->{-opt}->{dangerous};
return (undef, warn "FS_IOC_SETFLAGS: $!");
}
+# returns "0 but true" on success, undef on noop, true != 0 on failure
+sub yesdatacow_fh ($) {
+ my ($fh) = @_;
+ return unless is_btrfs $fh;
+ $FS_IOC_GETFLAGS //
+ return (undef, warn 'FS_IOC_GETFLAGS undefined for platform');
+ ioctl($fh, $FS_IOC_GETFLAGS, my $buf = "\0\0\0\0") //
+ return (undef, warn "FS_IOC_GETFLAGS: $!");
+ my $attr = unpack('l!', $buf);
+ return unless ($attr & 0x00800000); # FS_NOCOW_FL;
+ ioctl($fh, $FS_IOC_SETFLAGS, pack('l', $attr & ~0x00800000)) //
+ return (undef, warn "FS_IOC_SETFLAGS: $!");
+}
+
sub nodatacow_dir ($) {
my ($f) = @_;
if (open my $fh, '<', $f) {
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
package PublicInbox::Xapcmd;
use v5.12;
-use autodie qw(chmod opendir rename syswrite);
+use autodie qw(chmod closedir open opendir rename syswrite);
use PublicInbox::Spawn qw(which popen_rd);
use PublicInbox::Syscall;
use PublicInbox::Admin qw(setup_signals);
use PublicInbox::SearchIdx;
use File::Temp 0.19 (); # ->newdir
use File::Path qw(remove_tree);
-use POSIX qw(WNOHANG _exit);
+use POSIX qw(WNOHANG dup _exit);
use PublicInbox::DS;
# support testing with dev versions of Xapian which installs
my $v = PublicInbox::Search::SCHEMA_VERSION();
my $wip = File::Temp->newdir("xapian$v-XXXX", DIR => $dir);
$tmp->{$old} = $wip;
- PublicInbox::Syscall::nodatacow_dir($wip->dirname);
+ $opt->{cow} or
+ PublicInbox::Syscall::nodatacow_dir($wip->dirname);
push @queue, [ $old, $wip ];
} elsif (defined $old) {
opendir(my $dh, $old);
warn "W: skipping unknown dir: $old/$dn\n"
}
}
+ if ($opt->{cow}) { # make existing $DIR/{xap,ei}* CoW
+ my $dfd = dup(fileno($dh)) // die "dup: $!";
+ open my $fh, '<&='.$dfd;
+ closedir $dh;
+ PublicInbox::Syscall::yesdatacow_fh($fh);
+ }
die "No Xapian shards found in $old\n" unless @old_shards;
@old_shards = sort { $a <=> $b } @old_shards;
my ($src, $max_shard);
}
foreach my $dn (0..$max_shard) {
my $wip = File::Temp->newdir("$dn-XXXX", DIR => $old);
- same_fs_or_die($old, $wip->dirname);
+ my $wip_dn = $wip->dirname;
+ same_fs_or_die($old, $wip_dn);
my $cur = "$old/$dn";
push @queue, [ $src // $cur , $wip ];
- PublicInbox::Syscall::nodatacow_dir($wip->dirname);
+ $opt->{cow} or
+ PublicInbox::Syscall::nodatacow_dir($wip_dn);
$tmp->{$cur} = $wip;
}
# mark old shards to be unlinked
(\%PublicInbox::Search::X, $flag);
}
-sub compact_tmp_shard ($) {
- my ($wip) = @_;
+sub compact_tmp_shard ($$) {
+ my ($wip, $opt) = @_;
my $new = $wip->dirname;
my ($dir) = ($new =~ m!(.*?/)[^/]+/*\z!);
same_fs_or_die($dir, $new);
my $ft = File::Temp->newdir("$new.compact-XXXX", DIR => $dir);
- PublicInbox::Syscall::nodatacow_dir($ft->dirname);
+ PublicInbox::Syscall::nodatacow_dir($ft->dirname) if !$opt->{cow};
$ft;
}
my @tmp;
my @dst = map {
my $wip = $_->[1];
- my $tmp = $opt->{compact} ? compact_tmp_shard($wip) : $wip;
+ my $tmp = $opt->{compact} ?
+ compact_tmp_shard($wip, $opt) : $wip;
push @tmp, $tmp;
$X->{WritableDatabase}->new($tmp->dirname, $flag);
} @$queue;
my $tmp = $wip;
local @SIG{keys %SIG} = values %SIG;
if ($opt->{compact}) {
- $tmp = compact_tmp_shard($wip);
+ $tmp = compact_tmp_shard($wip, $opt);
setup_signals();
}
EOF
my $opt = { fsync => 1, scan => 1 }; # --no-scan is hidden
GetOptions($opt, qw(quiet|q verbose|v+ reindex jobs|j=i fsync|sync! dangerous
- indexlevel|index-level|L=s join:s@
+ cow! indexlevel|index-level|L=s join:s@
batch_size|batch-size=s max_size|max-size=s
include|I=s@ only=s@ all show:s@
project-list=s exclude=s@ project-root|r=s
# index options
qw(verbose|v+ rethread compact|c+ fsync|sync!
indexlevel|index-level|L=s max_size|max-size=s
- batch_size|batch-size=s wal
+ batch_size|batch-size=s cow! wal
sequential-shard|seq-shard
)) or die $help;
if ($opt->{help}) { print $help; exit 0 };
EOF
my $opt = { quiet => -1, compact => 0, fsync => 1, scan => 1 };
GetOptions($opt, qw(verbose|v+ reindex rethread compact|c+ jobs|j=i
- fsync|sync! fast dangerous wal defrag=i
+ fsync|sync! fast dangerous cow! wal defrag=i
indexlevel|index-level|L=s max_size|max-size=s
batch_size|batch-size=s
dedupe:s@ gc commit-interval=i watch scan! dry-run|n
'update-extindex' => [], # ":s@" optional arg sets '' if no arg given
};
GetOptions($opt, qw(verbose|v+ reindex rethread compact|c+ jobs|j=i prune
- fsync|sync! xapian_only|xapian-only dangerous wal
+ fsync|sync! xapian_only|xapian-only dangerous cow! wal
indexlevel|index-level|L=s max_size|max-size=s
defrag=i batch_size|batch-size=s
since|after=s until|before=s
exit 1;
};
GetOptions(my $opt = {}, qw(version|V=i
- wal indexlevel|index-level|L=s
+ cow! wal indexlevel|index-level|L=s
skip-epoch|skip|S=i skip-artnum=i
jobs|j=i newsgroup|ng=s
skip-docdata help|h
my $opt = { quiet => -1, compact => 0, fsync => 1,
-eidx_ok => 1, -cidx_ok => 1 };
GetOptions($opt, qw(
- fsync|sync! compact|c reshard|R=i
+ cow! fsync|sync! compact|c reshard|R=i
max_size|max-size=s batch_size|batch-size=s
sequential-shard|seq-shard
jobs|j=i quiet|q verbose|v
--- /dev/null
+# Copyright (C) all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+use v5.12; use autodie; use PublicInbox::TestCommon;
+use File::Temp 0.19;
+use PublicInbox::IO;
+my $dir = $ENV{BTRFS_TESTDIR};
+plan skip_all => 'BTRFS_TESTDIR not defined' if !$dir;
+plan skip_all => 'test is Linux-only' if $^O ne 'linux';
+require_mods 'v2';
+my $lsattr = require_cmd 'lsattr';
+my $tmp = File::Temp->newdir('cow-XXXX', DIR => $dir);
+local $ENV{PI_CONFIG} = "$tmp/pi-cfg";
+
+PublicInbox::IO::write_file '>', $ENV{PI_CONFIG}, <<EOM;
+[publicinboxmda]
+ spamcheck = none
+EOM
+
+my $addr = 'a@example.com';
+my $baddr = 'b@example.com';
+my $a2ddr = 'a2@example.com';
+my $b2addr = 'b2@example.com';
+
+run_script([qw(-init -L medium a),
+ "$tmp/a", qw(https://example.com/a), $addr]);
+
+my $lsa = xqx([$lsattr, '-l', "$tmp/a/public-inbox"]);
+like $lsa, qr/\bNo_COW\b/, 'No_COW set by default (v1)' or
+ diag explain($lsa);
+
+run_script([qw(-init --cow -L medium b),
+ "$tmp/b", qw(https://example.com/b), $baddr]);
+
+$lsa = xqx([$lsattr, '-l', "$tmp/b/public-inbox"]);
+unlike $lsa, qr/\bNo_COW\b/, 'No_COW not set' or
+ diag explain($lsa);
+
+run_script([qw(-init -V2 -L medium a2),
+ "$tmp/a2", qw(https://example.com/a2 a2@example.com)]);
+$lsa = xqx([$lsattr, '-l', "$tmp/a2/", glob("$tmp/a2/xap*/")]);
+like $lsa, qr/\bNo_COW\b/, 'No_COW set by default (v2)' or
+ diag explain($lsa);
+
+ok run_script([qw(-init --cow -V2 -L medium b2),
+ "$tmp/b2", qw(https://example.com/b2 b2@example.com)]);
+$lsa = xqx([$lsattr, '-l', "$tmp/b2/", glob("$tmp/b2/xap*/")]);
+unlike $lsa, qr/\bNo_COW\b/, 'No_COW not set with --cow';
+
+ok run_script([qw(-init -L basic --cow c),
+ "$tmp/c", qw(https://example.com/c c@example.com)]),
+ '-init -V1 w/o CoW';
+
+my $eml = eml_load 't/plack-qp.eml';
+run_script([qw(-mda --no-precheck)],
+ { ORIGINAL_RECIPIENT => 'c@example.com' },
+ { 0 => \($eml->as_string) });
+
+$lsa = xqx([$lsattr, '-l', "$tmp/b/public-inbox"]);
+unlike $lsa, qr/\bNo_COW\b/, 'No_COW not set' or
+ diag explain($lsa);
+
+ok run_script([qw(-convert --cow), "$tmp/c", "$tmp/c2"]),
+ '-convert --cow';
+$lsa = xqx([$lsattr, '-lR', glob("$tmp/c2/xap*/")]);
+unlike $lsa, qr/\bNo_COW\b/i, 'CoW preserved w/ -convert --cow';
+
+ok run_script([qw(-convert), "$tmp/c", "$tmp/C2"]),
+ '-convert w/o --cow';
+$lsa = xqx([$lsattr, '-lR', glob("$tmp/C2/xap*/")]);
+like $lsa, qr/\bNo_COW\b/i, '-convert unsets CoW w/o --cow';
+
+ok run_script([qw(-index --cow -L medium), "$tmp/c2"]),
+ '-index -V2 --cow + Xapian';
+$lsa = xqx([$lsattr, '-lR', "$tmp/c2/", glob("$tmp/c2/xap*/")]);
+unlike $lsa, qr/\bNo_COW\b/i, 'CoW preserved w/ -convert --cow + Xapian';
+
+ok run_script([qw(-xcpdb -R1 --cow), "$tmp/c2"]),
+ 'xcpdb respects --cow';
+$lsa = xqx([$lsattr, '-lR', glob("$tmp/c2/xap*/")]);
+unlike $lsa, qr/\bNo_COW\b/i, '-xcpdb --cow works';
+
+done_testing;
my $smsg = $es->over->next_by_mid($msgid, \(my $id), \(my $prev));
ok $smsg, 'new message imported into over.sqlite3 w/ basic';
}
+SKIP: {
+ my $bdir = $ENV{BTRFS_TESTDIR} or skip 'BTRFS_TESTDIR not defined', 1;
+ my $lsattr = require_cmd 'lsattr', 1;
+ my $tmp = File::Temp->newdir('eidx-cow-XXXX', DIR => $bdir);
+ local $ENV{DUMP} = 1;
+ ok run_script([qw(-extindex --cow --all), "$tmp/eidx"], undef,
+ { 2 => \(my $err = '') }), 'extindexed w/ --cow';
+ diag $err;
+ my $lsa = xqx([$lsattr, '-Rl', glob("$tmp/eidx/ei*")]);
+ unlike $lsa, qr/No_COW/i, '--cow respected';
+}
done_testing;
PublicInbox::Syscall::nodatacow_dir($name);
is_deeply \@w, [], 'no warnings if CoW already disabled';
}
+ open $fh, '<', $name or BAIL_OUT "open($name): $!";
+ PublicInbox::Syscall::yesdatacow_fh($fh);
+ $res = xqx([$lsattr, '-d', $name]);
+ like $res, qr/^-+ \Q$name\E/, "`C' attribute cleared";
};
done_testing;