v1-0001-Add-a-WIP-corruption-checker.patch
application/octet-stream
Filename: v1-0001-Add-a-WIP-corruption-checker.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v1-0001
Subject: Add a WIP corruption checker
| File | + | − |
|---|---|---|
| contrib/amcheck/t/006_corrupt_idx.pl | 136 | 0 |
From cc7bf7991ff827c4b41c0cd7888a073b91f66827 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Fri, 17 May 2024 10:24:11 -0700
Subject: [PATCH v1] Add a WIP corruption checker
To help analyze Alexander Korotkov's v3 series of patches, add a
corruption checker that runs an infinite loop corrupting an index
and seeing if the corruption is detected.
THIS IS NOT FOR COMMIT.
---
contrib/amcheck/t/006_corrupt_idx.pl | 136 +++++++++++++++++++++++++++
1 file changed, 136 insertions(+)
create mode 100644 contrib/amcheck/t/006_corrupt_idx.pl
diff --git a/contrib/amcheck/t/006_corrupt_idx.pl b/contrib/amcheck/t/006_corrupt_idx.pl
new file mode 100644
index 0000000000..d27b0ff9b6
--- /dev/null
+++ b/contrib/amcheck/t/006_corrupt_idx.pl
@@ -0,0 +1,136 @@
+
+# Copyright (c) 2023-2024, PostgreSQL Global Development Group
+
+# This regression test checks the behavior of the btree validation in the
+# presence of breaking sort order changes.
+#
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Fcntl 'SEEK_SET';
+
+my $node = PostgreSQL::Test::Cluster->new('test');
+$node->init;
+$node->append_conf('postgresql.conf', 'autovacuum = off');
+$node->start;
+
+# Create tables and indexes over some values
+$node->safe_psql('postgres',qq(
+CREATE EXTENSION amcheck;
+CREATE TABLE tbl (i TEXT);
+INSERT INTO tbl (SELECT 'MAGIC_' || gs::TEXT FROM generate_series(1,100000) gs ORDER BY RANDOM());
+CREATE UNIQUE INDEX idx ON tbl (i);
+));
+
+my @blocks;
+
+while (1)
+{
+ my ($result, $stdout, $stderr);
+
+ $result = $node->safe_psql(
+ 'postgres', q(
+ SELECT bt_index_parent_check('idx', true, true, true);
+ ));
+ is($result, '', 'run amcheck on non-broken idx');
+
+ my $pgdata = $node->data_dir;
+ my $rel = $node->safe_psql('postgres',
+ qq(SELECT pg_relation_filepath('public.idx')));
+ my $relpath = "$pgdata/$rel";
+ $node->stop;
+
+ my ($blksize, @blocks) = read_blocks($relpath);
+
+ my $ttl = 1000;
+ my $corrupted_blkno = undef;
+ while (!defined($corrupted_blkno) && $ttl--)
+ {
+ my $blkno = int(rand(scalar(@blocks)-1));
+
+ if ($blocks[$blkno] =~ m/.*MAGIC_(\d+)/)
+ {
+ my $magic = $1;
+ my $corrupted_block = $blocks[$blkno];
+
+ my $next_magic = $magic + 1;
+ my $prev_magic = $magic - 1;
+ if ($next_magic >= 1 && $next_magic <= 100000 && $blocks[$blkno+1] =~ m/MAGIC_$next_magic/)
+ {
+ if ($corrupted_block =~ s/MAGIC_$magic/MAGIC_$next_magic/)
+ {
+ write_block($relpath, $blksize, $blkno, $corrupted_block);
+ $corrupted_blkno = $blkno;
+ }
+ }
+ elsif ($prev_magic >= 1 && $prev_magic <= 100000 && $blocks[$blkno+1] =~ m/MAGIC_$prev_magic/)
+ {
+ if ($corrupted_block =~ s/MAGIC_$magic/MAGIC_$prev_magic/)
+ {
+ write_block($relpath, $blksize, $blkno, $corrupted_block);
+ $corrupted_blkno = $blkno;
+ }
+ }
+ }
+ }
+
+ BAIL_OUT("Failed to corrupt anything") unless($ttl > 0);
+
+ # Ok, we've corrupted the file. Restart the node and see if the
+ # corruption checker notices anything.
+ $node->start;
+
+ ($result, $stdout, $stderr) = $node->psql(
+ 'postgres', q(
+ SELECT bt_index_parent_check('idx', true, true, true);
+ ));
+ ok( $stderr =~ /item order invariant violated for index "idx"|index uniqueness is violated for index "idx"|could not find tuple using search from root page in index "idx"|mismatch between parent key and child high key in index "idx"|detected uniqueness violation for index "idx"/);
+
+ # Repair the damage.
+ $node->stop;
+ write_block($relpath, $blksize, $corrupted_blkno, $blocks[$corrupted_blkno]);
+
+ # Restart the database and confirm the index is back to passing
+ $node->start;
+ $result = $node->safe_psql(
+ 'postgres', q(
+ SELECT bt_index_check('idx', true, true);
+ ));
+ is($result, '', 'run amcheck on non-broken idx');
+}
+
+# Not reached
+done_testing();
+
+sub read_blocks
+{
+ my ($relpath) = @_;
+ my $file;
+ open($file, '+<', $relpath)
+ or BAIL_OUT("open failed: $!");
+ binmode $file;
+ my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($file);
+
+ my @result;
+ for (my $blkno = 0; $blkno < $blocks; $blkno++)
+ {
+ sysseek($file, $blkno * $blksize, SEEK_SET);
+ sysread($file, $result[$blkno], $blksize);
+ }
+ close($file);
+ return ($blksize, @result);
+}
+
+sub write_block
+{
+ my ($relpath, $blksize, $blkno, $block) = @_;
+ my $file;
+ open($file, '+<', $relpath)
+ or BAIL_OUT("open failed: $!");
+ binmode $file;
+ sysseek($file, $blkno * $blksize, SEEK_SET);
+ syswrite($file, $block);
+ close($file);
+}
--
2.39.3 (Apple Git-145)