diff --git a/contrib/pg_tde/t/pg_resetwal_basic.pl b/contrib/pg_tde/t/pg_resetwal_basic.pl new file mode 100644 index 0000000000000..78890f633cf83 --- /dev/null +++ b/contrib/pg_tde/t/pg_resetwal_basic.pl @@ -0,0 +1,150 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +unlink('/tmp/pg_resetwal_basic.per'); + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf( + 'postgresql.conf', q{ +track_commit_timestamp = on + +# WAL Encryption +shared_preload_libraries = 'pg_tde' +}); + +$node->start; +$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); +$node->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_resetwal_basic.per');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); + +$node->append_conf( + 'postgresql.conf', q{ +pg_tde.wal_encrypt = on +}); +$node->stop; + + +command_like([ 'pg_resetwal', '-n', $node->data_dir ], + qr/checkpoint/, 'pg_resetwal -n produces output'); + + +# Permissions on PGDATA should be default +SKIP: +{ + skip "unix-style permissions not supported on Windows", 1 + if ($windows_os); + + ok(check_mode_recursive($node->data_dir, 0700, 0600), + 'check PGDATA permissions'); +} + +command_ok([ 'pg_resetwal', '-D', $node->data_dir ], 'pg_resetwal runs'); +$node->start; +is($node->safe_psql("postgres", "SELECT 1;"), + 1, 'server running and working after reset'); + +command_fails_like( + [ 'pg_resetwal', $node->data_dir ], + qr/lock file .* exists/, + 'fails if server running'); + +$node->stop('immediate'); +command_fails_like( + [ 'pg_resetwal', $node->data_dir ], + qr/database server was not shut down cleanly/, + 'does not run after immediate shutdown'); +command_ok( + [ 'pg_resetwal', '-f', $node->data_dir ], + 'runs after immediate shutdown with force'); +$node->start; +is($node->safe_psql("postgres", "SELECT 1;"), + 1, 'server running and working after forced reset'); + +$node->stop; + +# check various command-line handling + +# Note: This test intends to check that a nonexistent data directory +# gives a reasonable error message. Because of the way the code is +# currently structured, you get an error about readings permissions, +# which is perhaps suboptimal, so feel free to update this test if +# this gets improved. + + +# run with control override options + +my $out = (run_command([ 'pg_resetwal', '-n', $node->data_dir ]))[0]; +$out =~ /^Database block size: *(\d+)$/m or die; +my $blcksz = $1; + +my @cmd = ('pg_resetwal', '-D', $node->data_dir); + +# some not-so-critical hardcoded values +push @cmd, '-e', 1; +push @cmd, '-l', '00000001000000320000004B'; +push @cmd, '-o', 100_000; +push @cmd, '--wal-segsize', 1; + +# these use the guidance from the documentation + +sub get_slru_files +{ + opendir(my $dh, $node->data_dir . '/' . $_[0]) or die $!; + my @files = sort grep { /[0-9A-F]+/ } readdir $dh; + closedir $dh; + return @files; +} + +my (@files, $mult); + +@files = get_slru_files('pg_commit_ts'); +# XXX: Should there be a multiplier, similar to the other options? +# -c argument is "old,new" +push @cmd, + '-c', + sprintf("%d,%d", hex($files[0]) == 0 ? 3 : hex($files[0]), hex($files[-1])); + +@files = get_slru_files('pg_multixact/offsets'); +$mult = 32 * $blcksz / 4; +# -m argument is "new,old" +push @cmd, '-m', + sprintf("%d,%d", + (hex($files[-1]) + 1) * $mult, + hex($files[0]) == 0 ? 1 : hex($files[0] * $mult)); + +@files = get_slru_files('pg_multixact/members'); +$mult = 32 * int($blcksz / 20) * 4; +push @cmd, '-O', (hex($files[-1]) + 1) * $mult; + +@files = get_slru_files('pg_xact'); +$mult = 32 * $blcksz * 4; +push @cmd, + '-u', (hex($files[0]) == 0 ? 3 : hex($files[0]) * $mult), + '-x', ((hex($files[-1]) + 1) * $mult); + +command_ok([ @cmd, '-n' ], 'runs with control override options, dry run'); +command_ok(\@cmd, 'runs with control override options'); +command_like( + [ 'pg_resetwal', '-n', $node->data_dir ], + qr/^Latest checkpoint's NextOID: *100000$/m, + 'spot check that control changes were applied'); + +$node->start; +ok(1, 'server started after reset'); + +done_testing(); diff --git a/contrib/pg_tde/t/pg_resetwal_corrupted.pl b/contrib/pg_tde/t/pg_resetwal_corrupted.pl new file mode 100644 index 0000000000000..74cc66a2c8030 --- /dev/null +++ b/contrib/pg_tde/t/pg_resetwal_corrupted.pl @@ -0,0 +1,92 @@ + +# Copyright (c) 2021-2024, PostgreSQL Global Development Group + +# Tests for handling a corrupted pg_control + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +unlink('/tmp/pg_resetwal_corrupted.per'); + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf( + 'postgresql.conf', q{ + +# WAL Encryption +shared_preload_libraries = 'pg_tde' +}); + +$node->start; +$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;"); +$node->safe_psql('postgres', + "SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_corrupted.per');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); +$node->safe_psql('postgres', + "SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');" +); + +$node->append_conf( + 'postgresql.conf', q{ +pg_tde.wal_encrypt = on +}); +$node->stop; + +my $pg_control = $node->data_dir . '/global/pg_control'; +my $size = -s $pg_control; + +# Read out the head of the file to get PG_CONTROL_VERSION in +# particular. +my $data; +open my $fh, '<', $pg_control or BAIL_OUT($!); +binmode $fh; +read $fh, $data, 16 or die $!; +close $fh; + +# Fill pg_control with zeros +open $fh, '>', $pg_control or BAIL_OUT($!); +binmode $fh; +print $fh pack("x[$size]"); +close $fh; + +command_checks_all( + [ 'pg_resetwal', '-n', $node->data_dir ], + 0, + [qr/pg_control version number/], + [ + qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/ + ], + 'processes corrupted pg_control all zeroes'); + +# Put in the previously saved header data. This uses a different code +# path internally, allowing us to process a zero WAL segment size. +open $fh, '>', $pg_control or BAIL_OUT($!); +binmode $fh; +print $fh $data, pack("x[" . ($size - 16) . "]"); +close $fh; + +command_checks_all( + [ 'pg_resetwal', '-n', $node->data_dir ], + 0, + [qr/pg_control version number/], + [ + qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/ + ], + 'processes zero WAL segment size'); + +# now try to run it +command_fails_like( + [ 'pg_resetwal', $node->data_dir ], + qr/not proceeding because control file values were guessed/, + 'does not run when control file values were guessed'); +command_ok([ 'pg_resetwal', '-f', $node->data_dir ], + 'runs with force when control file values were guessed'); + +done_testing(); diff --git a/src/bin/pg_resetwal/.gitignore b/src/bin/pg_resetwal/.gitignore index 56bade5ea4401..ddb4f6fb35745 100644 --- a/src/bin/pg_resetwal/.gitignore +++ b/src/bin/pg_resetwal/.gitignore @@ -1,2 +1,5 @@ /pg_resetwal /tmp_check/ + +# Source files copied from src/backend/access/transam/ +/xlogreader.c diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile index 4228a5a772a9f..f39e8378417ce 100644 --- a/src/bin/pg_resetwal/Makefile +++ b/src/bin/pg_resetwal/Makefile @@ -21,8 +21,23 @@ OBJS = \ $(WIN32RES) \ pg_resetwal.o +ifeq ($(enable_percona_ext),yes) +override CPPFLAGS := -DFRONTEND $(CPPFLAGS) + +OBJS += \ + $(top_srcdir)/src/fe_utils/simple_list.o \ + $(top_builddir)/src/libtde/libtdexlog.a \ + $(top_builddir)/src/libtde/libtde.a \ + xlogreader.o + +override CPPFLAGS := -I$(top_srcdir)/contrib/pg_tde/src/include -I$(top_srcdir)/contrib/pg_tde/src/libkmip/libkmip/include $(CPPFLAGS) +endif + all: pg_resetwal +xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/% + rm -f $@ && $(LN_S) $< . + pg_resetwal: $(OBJS) | submake-libpgport $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) @@ -36,7 +51,7 @@ uninstall: rm -f '$(DESTDIR)$(bindir)/pg_resetwal$(X)' clean distclean: - rm -f pg_resetwal$(X) $(OBJS) + rm -f pg_resetwal$(X) $(OBJS) xlogreader.c rm -rf tmp_check check: diff --git a/src/bin/pg_resetwal/meson.build b/src/bin/pg_resetwal/meson.build index c1239528db27f..ea7a3094452d5 100644 --- a/src/bin/pg_resetwal/meson.build +++ b/src/bin/pg_resetwal/meson.build @@ -10,10 +10,22 @@ if host_system == 'windows' '--FILEDESC', 'pg_resetwal - reset PostgreSQL WAL log']) endif +link_w = [] +include_dirs = [postgres_inc] + +if percona_ext == true + link_w = [pg_tde_frontend] + include_dirs = [postgres_inc, pg_tde_inc] + pg_resetwal_sources += xlogreader_sources +endif + pg_resetwal = executable('pg_resetwal', pg_resetwal_sources, dependencies: [frontend_code], + c_args: ['-DFRONTEND'], # needed for xlogreader et al kwargs: default_bin_args, + include_directories: include_dirs, + link_with: link_w ) bin_targets += pg_resetwal diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c index e9dcb5a6d89d1..9ce4ac43d3c68 100644 --- a/src/bin/pg_resetwal/pg_resetwal.c +++ b/src/bin/pg_resetwal/pg_resetwal.c @@ -59,6 +59,13 @@ #include "pg_getopt.h" #include "storage/large_object.h" +#ifdef PERCONA_EXT +#include "pg_tde.h" +#include "access/pg_tde_fe_init.h" +#include "access/pg_tde_xlog_smgr.h" +#include "access/xlog_smgr.h" +#endif + static ControlFileData ControlFile; /* pg_control values */ static XLogSegNo newXlogSegNo; /* new XLOG segment # */ static bool guessed = false; /* T if we had to guess at any values */ @@ -344,6 +351,15 @@ main(int argc, char *argv[]) } #endif +#ifdef PERCONA_EXT + { + char tde_path[MAXPGPATH]; + snprintf(tde_path, sizeof(tde_path), "%s/%s", DataDir, PG_TDE_DATA_DIR); + pg_tde_fe_init(tde_path); + TDEXLogSmgrInit(); + } +#endif + get_restricted_token(); /* Set mask based on PGDATA permissions */ @@ -488,6 +504,22 @@ main(int argc, char *argv[]) exit(1); } +#ifdef PERCONA_EXT + /* + * The only thing that WAL will contain after reset is a checkpoint, so we can + * write it in unencrypted form. There is no sensitive data in it. But we still + * need to use TDE smgr because WAL key change may be needed. If the last WAL key + * had type "encrypted" new key will be created with type "unencrypted" to mark + * the beginning of a new unencrypted WAL record. If the last WAL key had type + * "unencrypted" it will be reused. On the startup server may create a new key + * with appropriate type according to encryption settings. + * + * We are doing a write initialization only here and not at the startup because we + * want to be sure that everything is checked and ready for writing at this point. + */ + TDEXLogSmgrInitWrite(false); +#endif + /* * Else, do the dirty deed. */ @@ -1134,7 +1166,13 @@ WriteEmptyXLOG(void) pg_fatal("could not open file \"%s\": %m", path); errno = 0; +#ifdef PERCONA_EXT + if (xlog_smgr->seg_write(fd, buffer.data, XLOG_BLCKSZ, 0, + ControlFile.checkPointCopy.ThisTimeLineID, + newXlogSegNo, WalSegSz) != XLOG_BLCKSZ) +#else if (write(fd, buffer.data, XLOG_BLCKSZ) != XLOG_BLCKSZ) +#endif { /* if write didn't set errno, assume problem is no disk space */ if (errno == 0) @@ -1142,6 +1180,11 @@ WriteEmptyXLOG(void) pg_fatal("could not write file \"%s\": %m", path); } +#ifdef PERCONA_EXT + /* If we used xlog smgr, we need to update the file offset */ + lseek(fd, XLOG_BLCKSZ, SEEK_CUR); +#endif + /* Fill the rest of the file with zeroes */ memset(buffer.data, 0, XLOG_BLCKSZ); for (nbytes = XLOG_BLCKSZ; nbytes < WalSegSz; nbytes += XLOG_BLCKSZ)