From 76ee3b3c375c32055edff86f6348af007012a922 Mon Sep 17 00:00:00 2001 From: Nitin Motiani Date: Sat, 15 Feb 2025 04:29:17 +0000 Subject: [PATCH v16 4/5] Add tests for pipe * These tests include the invalid usages of --pipe-command with other flags. * Also test pg_dump and pg_restore with pipe command along with various other flags. --- src/bin/pg_dump/t/001_basic.pl | 72 ++++- src/bin/pg_dump/t/002_pg_dump.pl | 292 +++++++++++++++++++- src/bin/pg_dump/t/004_pg_dump_parallel.pl | 43 +++ src/bin/pg_dump/t/005_pg_dump_filterfile.pl | 18 ++ 4 files changed, 415 insertions(+), 10 deletions(-) diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl index 687e842cde9..92d47e4fd93 100644 --- a/src/bin/pg_dump/t/001_basic.pl +++ b/src/bin/pg_dump/t/001_basic.pl @@ -74,6 +74,48 @@ command_fails_like( 'pg_dump: options --statistics-only and --no-statistics cannot be used together' ); +command_fails_like( + [ 'pg_dump', '-Fd', '--pipe="cat"', '-f', 'testdir', 'test'], + qr/\Qpg_dump: error: options -f\/--file and --pipe cannot be used together\E/, + 'pg_dump: options -f/--file and --pipe cannot be used together' +); + +command_fails_like( + [ 'pg_dump', '-Fd', '--pipe="cat"', '-Z', 'gzip', 'test'], + qr/\Qpg_dump: error: option --pipe is not supported with any compression type\E/, + 'pg_dump: option --pipe is not supported with any compression type' +); + +command_fails_like( + [ 'pg_dump', '-Fd', '--pipe="cat"', '--compress=lz4', 'test'], + qr/\Qpg_dump: error: option --pipe is not supported with any compression type\E/, + 'pg_dump: option --pipe is not supported with any compression type' +); + +command_fails_like( + [ 'pg_dump', '-Fd', '--pipe="cat"', '--compress=gzip', 'test'], + qr/\Qpg_dump: error: option --pipe is not supported with any compression type\E/, + 'pg_dump: option --pipe is not supported with any compression type' +); + +command_fails_like( + [ 'pg_dump', '-Fd', '--pipe="cat"', '-Z', '1', 'test'], + qr/\Qpg_dump: error: option --pipe is not supported with any compression type\E/, + 'pg_dump: option --pipe is not supported with any compression type' +); + +command_fails_like( + [ 'pg_dump', '-Fc', '--pipe="cat"', 'test'], + qr/\Qpg_dump: error: option --pipe is only supported with directory format\E/, + 'pg_dump: option --pipe is only supported with directory format' +); + +command_fails_like( + [ 'pg_dump', '--format=tar', '--pipe="cat"', 'test'], + qr/\Qpg_dump: error: option --pipe is only supported with directory format\E/, + 'pg_dump: option --pipe is only supported with directory format' +); + command_fails_like( [ 'pg_dump', '-j2', '--include-foreign-data=xxx' ], qr/\Qpg_dump: error: option --include-foreign-data is not supported with parallel backup\E/, @@ -94,12 +136,38 @@ command_fails_like( command_fails_like( [ 'pg_restore', '-d', 'xxx', '-f', 'xxx' ], qr/\Qpg_restore: error: options -d\/--dbname and -f\/--file cannot be used together\E/, - 'pg_restore: options -d/--dbname and -f/--file cannot be used together'); + 'pg_restore: options -d/--dbname and -f/--file cannot be used together' +); + +command_fails_like( + [ 'pg_restore', '-f', '-', '--pipe="cat"', 'dumpdir' ], + qr/\Qpg_restore: error: cannot specify both an input file and --pipe\E/, + 'pg_restore: cannot specify both an input file and --pipe' +); + +command_fails_like( + [ 'pg_restore', '-Fd', '-f', '-', '--pipe="cat"', 'dumpdir' ], + qr/\Qpg_restore: error: cannot specify both an input file and --pipe\E/, + 'pg_restore: cannot specify both an input file and --pipe' +); + +command_fails_like( + [ 'pg_restore', '-Fc', '-f', '-', '--pipe="cat"' ], + qr/\Qpg_restore: error: option --pipe is only supported with directory format\E/, + 'pg_restore: option --pipe is only supported with directory format' +); + +command_fails_like( + [ 'pg_restore', '--format=tar', '-f', '-', '--pipe="cat"' ], + qr/\Qpg_restore: error: option --pipe is only supported with directory format\E/, + 'pg_restore: option --pipe is only supported with directory format' +); command_fails_like( [ 'pg_dump', '-c', '-a' ], qr/\Qpg_dump: error: options -c\/--clean and -a\/--data-only cannot be used together\E/, - 'pg_dump: options -c/--clean and -a/--data-only cannot be used together'); + 'pg_dump: options -c/--clean and -a/--data-only cannot be used together' +); command_fails_like( [ 'pg_dumpall', '-c', '-a' ], diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 3ee9fda50e4..51582b3caf7 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -7,8 +7,10 @@ use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; use Test::More; +use File::Spec; my $tempdir = PostgreSQL::Test::Utils::tempdir; +$tempdir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os; ############################################################### # Definition of the pg_dump runs to make. @@ -46,6 +48,69 @@ my $tempdir = PostgreSQL::Test::Utils::tempdir; my $supports_icu = ($ENV{with_icu} eq 'yes'); my $supports_gzip = check_pg_config("#define HAVE_LIBZ 1"); +# Use perl one-liner as a portable 'cat' replacement for Windows compatibility. +# On Windows, perl opens file handles in text mode by default, which corrupts +# binary archive data by translating newlines and interpreting EOF characters. +# We use -Mopen=IO,:raw to force raw binary mode. We use -pe 1 instead of +# -pe '' to avoid shell quoting issues with empty strings on Windows cmd.exe. +my $perlbin = $^X; +$perlbin =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os; +my $perl_cat = "\"$perlbin\" -Mopen=IO,:raw -pe 1"; + +# Check for external gzip program for pipe tests. +my $gzip_path = $ENV{GZIP_PROGRAM} || 'gzip'; +my $gzip_bin = "\"$gzip_path\""; +my $has_gzip_bin = + (system("$gzip_bin --version >" . File::Spec->devnull() . " 2>&1") == 0); + +# Pre-calculate complex pipe commands to keep the test definitions readable +# and ensure unified --pipe=... syntax for Windows stability. +# On Windows, we use double-layer quoting: internal quotes for paths with +# spaces, and an outer set of escaped quotes to protect shell operators +# like | and >. On other platforms, we avoid the outer wrap to satisfy /bin/sh. +my $is_win = $PostgreSQL::Test::Utils::windows_os; + +my $raw_pipe_defaults_dir = "$perl_cat > \"$tempdir/defaults_dir_format/%f\""; +my $raw_pipe_defaults_res = "$perl_cat \"$tempdir/defaults_dir_format/%f\""; +my $raw_pipe_cross_dump = "$perl_cat > \"$tempdir/pipe_cross_dump/%f\""; +my $raw_pipe_cross_restore = ($supports_gzip && !$is_win) + ? "if [ -f \"$tempdir/pipe_cross_restore/%f.gz\" ]; then $gzip_bin -d -c \"$tempdir/pipe_cross_restore/%f.gz\"; else $perl_cat \"$tempdir/pipe_cross_restore/%f\"; fi" + : "$perl_cat \"$tempdir/pipe_cross_restore/%f\""; +my $raw_pipe_parallel_out = "$perl_cat > \"$tempdir/pipe_out_dir_parallel/%f\""; +my $raw_pipe_parallel_in = "$perl_cat \"$tempdir/pipe_out_dir_parallel/%f\""; +my $raw_pipe_parallel_8_out = "$perl_cat > \"$tempdir/pipe_out_dir_parallel_8/%f\""; +my $raw_pipe_parallel_8_in = "$perl_cat \"$tempdir/pipe_out_dir_parallel_8/%f\""; +my $raw_pipe_complex_out = "$gzip_bin | $perl_cat > \"$tempdir/pipe_out_dir_complex/%f.gz\""; +my $raw_pipe_complex_in = "$perl_cat \"$tempdir/pipe_out_dir_complex/%f.gz\" | $gzip_bin -d"; +my $raw_pipe_lo_out = "$perl_cat > \"$tempdir/pipe_out_dir_lo/%f\""; +my $raw_pipe_lo_in = "$perl_cat \"$tempdir/pipe_out_dir_lo/%f\""; +my $raw_pipe_schema_out = "$perl_cat > \"$tempdir/schema_only_pipe_dir/%f\""; +my $raw_pipe_schema_in = "$perl_cat \"$tempdir/schema_only_pipe_dir/%f\""; + +my $pipe_defaults_dir = $is_win ? "\"$raw_pipe_defaults_dir\"" : $raw_pipe_defaults_dir; +my $pipe_defaults_res = $is_win ? "\"$raw_pipe_defaults_res\"" : $raw_pipe_defaults_res; +my $pipe_cross_dump = $is_win ? "\"$raw_pipe_cross_dump\"" : $raw_pipe_cross_dump; +my $pipe_cross_restore = $is_win ? "\"$raw_pipe_cross_restore\"" : $raw_pipe_cross_restore; +my $pipe_parallel_out = $is_win ? "\"$raw_pipe_parallel_out\"" : $raw_pipe_parallel_out; +my $pipe_parallel_in = $is_win ? "\"$raw_pipe_parallel_in\"" : $raw_pipe_parallel_in; +my $pipe_parallel_8_out = $is_win ? "\"$raw_pipe_parallel_8_out\"" : $raw_pipe_parallel_8_out; +my $pipe_parallel_8_in = $is_win ? "\"$raw_pipe_parallel_8_in\"" : $raw_pipe_parallel_8_in; +my $pipe_complex_out = $is_win ? "\"$raw_pipe_complex_out\"" : $raw_pipe_complex_out; +my $pipe_complex_in = $is_win ? "\"$raw_pipe_complex_in\"" : $raw_pipe_complex_in; +my $pipe_lo_out = $is_win ? "\"$raw_pipe_lo_out\"" : $raw_pipe_lo_out; +my $pipe_lo_in = $is_win ? "\"$raw_pipe_lo_in\"" : $raw_pipe_lo_in; +my $pipe_schema_out = $is_win ? "\"$raw_pipe_schema_out\"" : $raw_pipe_schema_out; +my $pipe_schema_in = $is_win ? "\"$raw_pipe_schema_in\"" : $raw_pipe_schema_in; + +# Create output directories for pipe tests +mkdir "$tempdir/pipe_out_dir_parallel"; +mkdir "$tempdir/pipe_out_dir_parallel_8"; +mkdir "$tempdir/pipe_out_dir_complex"; +mkdir "$tempdir/pipe_out_dir_lo"; +mkdir "$tempdir/pipe_cross_dump"; +mkdir "$tempdir/pipe_cross_restore"; +mkdir "$tempdir/schema_only_pipe_dir"; + my %pgdump_runs = ( binary_upgrade => { dump_cmd => [ @@ -223,6 +288,139 @@ my %pgdump_runs = ( ], }, + defaults_dir_format_pipe => { + test_key => 'defaults', + dump_cmd => [ + 'pg_dump', + '--format' => 'directory', + "--pipe=$pipe_defaults_dir", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/defaults_dir_format_pipe.sql", + "--pipe=$pipe_defaults_res", + '--statistics', + ], + }, + + defaults_dir_format_pipe_dump_only => { + test_key => 'defaults', + dump_cmd => [ + 'pg_dump', + '--format' => 'directory', + "--pipe=$pipe_cross_dump", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/defaults_dir_format_pipe_dump_only.sql", + '--statistics', + "$tempdir/pipe_cross_dump", + ], + }, + + defaults_dir_format_pipe_restore_only => { + test_key => 'defaults', + dump_cmd => [ + 'pg_dump', + '--format' => 'directory', + '--file' => "$tempdir/pipe_cross_restore", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/defaults_dir_format_pipe_restore_only.sql", + "--pipe=$pipe_cross_restore", + '--statistics', + ], + }, + + defaults_parallel_pipe => { + test_key => 'defaults', + dump_cmd => [ + 'pg_dump', + '--format' => 'directory', + '--jobs' => 2, + "--pipe=$pipe_parallel_out", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/defaults_parallel_pipe.sql", + "--pipe=$pipe_parallel_in", + '--statistics', + ], + }, + + defaults_parallel_8_pipe => { + test_key => 'defaults', + dump_cmd => [ + 'pg_dump', + '--format' => 'directory', + '--jobs' => 8, + "--pipe=$pipe_parallel_8_out", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/defaults_parallel_8_pipe.sql", + "--pipe=$pipe_parallel_8_in", + '--statistics', + ], + }, + + defaults_complex_pipe => { + test_key => 'defaults', + skip_unless => \$has_gzip_bin, + dump_cmd => [ + 'pg_dump', + '--format' => 'directory', + "--pipe=$pipe_complex_out", + '--statistics', + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/defaults_complex_pipe.sql", + "--pipe=$pipe_complex_in", + '--statistics', + ], + }, + + defaults_lo_pipe => { + test_key => 'defaults', + dump_cmd => [ + 'pg_dump', + '--format' => 'directory', + '--statistics', + "--pipe=$pipe_lo_out", + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/defaults_lo_pipe.sql", + '--statistics', + "--pipe=$pipe_lo_in", + ], + glob_patterns => [ + "$tempdir/pipe_out_dir_lo/toc.dat", + "$tempdir/pipe_out_dir_lo/blobs_*.toc", + ], + }, + # Do not use --no-sync to give test coverage for data sync. defaults_parallel => { test_key => 'defaults', @@ -527,6 +725,22 @@ my %pgdump_runs = ( 'postgres', ], }, + schema_only_pipe => { + test_key => 'schema_only', + dump_cmd => [ + 'pg_dump', '--no-sync', + '--format' => 'directory', + '--schema-only', + "--pipe=$pipe_schema_out", + 'postgres', + ], + restore_cmd => [ + 'pg_restore', + '--format' => 'directory', + '--file' => "$tempdir/schema_only_pipe.sql", + "--pipe=$pipe_schema_in", + ], + }, section_pre_data => { dump_cmd => [ 'pg_dump', '--no-sync', @@ -5212,25 +5426,24 @@ command_fails_like( ######################################### # Run all runs + foreach my $run (sort keys %pgdump_runs) { my $test_key = $run; - my $run_db = 'postgres'; + my $run_db = 'postgres'; $node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} }, "$run: pg_dump runs"); if ($pgdump_runs{$run}->{glob_patterns}) { - my $glob_patterns = $pgdump_runs{$run}->{glob_patterns}; - foreach my $glob_pattern (@{$glob_patterns}) + foreach my $glob_pattern (@{ $pgdump_runs{$run}->{glob_patterns} }) { - my @glob_output = glob($glob_pattern); my $ok = 0; - # certainly found some files if glob() returned multiple matches - $ok = 1 if (scalar(@glob_output) > 1); - # if just one match, we need to check if it's real - $ok = 1 if (scalar(@glob_output) == 1 && -f $glob_output[0]); + foreach my $file (glob("$glob_pattern")) + { + $ok = 1 if -e $file; + } is($ok, 1, "$run: glob check for $glob_pattern"); } } @@ -5334,6 +5547,69 @@ foreach my $run (sort keys %pgdump_runs) } } +######################################### +# Test error reporting for a failing pipe command. +# We use a perl one-liner that exits with 1 after processing input. +# This ensures we test the error handling in pclose() at the end of the dump, +# verifying that the child's exit status is correctly captured and reported. +my $failing_perl_cat = "\"$perlbin\" -Mopen=IO,:raw -pe \"END { exit 1 }\""; + +$node->command_fails_like( + [ 'pg_dump', '-Fd', $is_win ? "--pipe=\"$failing_perl_cat > \\\"%f\\\"\"" : "--pipe=$failing_perl_cat > \"%f\"", 'postgres' ], + qr/pipe command failed/, + 'pg_dump pipe command error reporting' +); + +$node->command_fails_like( + [ 'pg_restore', '-Fd', '-l', $is_win ? "--pipe=\"$failing_perl_cat \\\"$tempdir/pipe_cross_dump/%f\\\"\"" : "--pipe=$failing_perl_cat \"$tempdir/pipe_cross_dump/%f\"", ], + qr/pipe command failed/, + 'pg_restore pipe command error reporting' +); + +# Targeted Edge Case Tests +$node->command_fails_like( + [ 'pg_dump', '-Fd', '--pipe=/nonexistent/binary', 'postgres' ], + qr/could not write to file: (?:Broken pipe|The pipe has been ended)|Permission denied/, + 'pg_dump early pipe command execution failure' +); + +$node->command_fails_like( + [ 'pg_dump', '-Fd', '--pipe=no_such_command_at_all', 'postgres' ], + qr/could not write to file: (?:Broken pipe|The pipe has been ended)|not found|not recognized/, + 'pg_dump command not found error reporting' +); + +$node->command_fails_like( + [ 'pg_dump', '-Fd', '-f', '-', $is_win ? "--pipe=\"$perl_cat > \\\"%f\\\"\"" : "--pipe=$perl_cat > \"%f\"", 'postgres' ], + qr/options -f\/--file and --pipe cannot be used together/, + 'pg_dump options -f/--file and --pipe conflict check' +); + +# Test that pg_restore rejects a positional argument when --pipe is used. +# We create a dummy cluster archive (containing toc.glo) to verify that +# even in cluster mode, the mutual exclusivity holds. +mkdir "$tempdir/dummy_cluster_archive"; +open my $fh, '>', "$tempdir/dummy_cluster_archive/toc.glo"; +close $fh; + +$node->command_fails_like( + [ 'pg_restore', '-Fd', '-l', $is_win ? "--pipe=\"$perl_cat \\\"%f\\\"\"" : "--pipe=$perl_cat \"%f\"", "$tempdir/dummy_cluster_archive" ], + qr/cannot specify both an input file and --pipe/, + 'pg_restore --pipe rejects positional argument even for cluster archive' +); + +# Test that pg_dump --pipe bypasses local directory existence check. +# We use a pipe command that writes to a subdirectory that hasn't been created. +# The dump itself will fail when the pipe command tries to write to the +# non-existent directory, but the error should come from the pipe command/write +# failure, not from pg_dump's directory initialization. +my $remote_dir = "$tempdir/non_existent_remote_dir"; +$node->command_fails_like( + [ 'pg_dump', '-Fd', $is_win ? "--pipe=\"$perl_cat > \\\"$remote_dir/%f\\\"\"" : "--pipe=$perl_cat > \"$remote_dir/%f\"", 'postgres' ], + qr/could not write to file: (?:Broken pipe|The pipe has been ended)|pipe command failed/, + 'pg_dump --pipe bypasses local directory existence check' +); + ######################################### # Stop the database instance, which will be removed at the end of the tests. diff --git a/src/bin/pg_dump/t/004_pg_dump_parallel.pl b/src/bin/pg_dump/t/004_pg_dump_parallel.pl index 738f34b1c1b..63cd3ba016d 100644 --- a/src/bin/pg_dump/t/004_pg_dump_parallel.pl +++ b/src/bin/pg_dump/t/004_pg_dump_parallel.pl @@ -8,19 +8,31 @@ use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; use Test::More; +# Use perl one-liner as a portable 'cat' replacement for Windows compatibility. +# On Windows, perl opens file handles in text mode by default, which corrupts +# binary archive data by translating newlines and interpreting EOF characters. +# We use -Mopen=IO,:raw to force raw binary mode. We use -pe 1 instead of +# -pe '' to avoid shell quoting issues with empty strings on Windows cmd.exe. +my $perlbin = $^X; +$perlbin =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os; +my $perl_cat = "\"$perlbin\" -Mopen=IO,:raw -pe 1"; + my $dbname1 = 'regression_src'; my $dbname2 = 'regression_dest1'; my $dbname3 = 'regression_dest2'; +my $dbname4 = 'regression_dest3'; my $node = PostgreSQL::Test::Cluster->new('main'); $node->init; $node->start; my $backupdir = $node->backup_dir; +$backupdir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os; $node->run_log([ 'createdb', $dbname1 ]); $node->run_log([ 'createdb', $dbname2 ]); $node->run_log([ 'createdb', $dbname3 ]); +$node->run_log([ 'createdb', $dbname4 ]); $node->safe_psql( $dbname1, @@ -87,4 +99,35 @@ $node->command_ok( ], 'parallel restore as inserts'); +mkdir "$backupdir/dump_pipe"; + +# Pre-calculate pipe commands for readability and unified syntax. +# Use double-layer quoting only on Windows to protect shell operators. +my $is_win = $PostgreSQL::Test::Utils::windows_os; +my $raw_pipe_dump = "$perl_cat > \"$backupdir/dump_pipe/%f\""; +my $raw_pipe_restore = "$perl_cat \"$backupdir/dump_pipe/%f\""; + +my $pipe_dump = $is_win ? "\"$raw_pipe_dump\"" : $raw_pipe_dump; +my $pipe_restore = $is_win ? "\"$raw_pipe_restore\"" : $raw_pipe_restore; + +$node->command_ok( + [ + 'pg_dump', + '--format' => 'directory', + '--no-sync', + '--jobs' => 2, + "--pipe=$pipe_dump", + $node->connstr($dbname1), + ], + 'parallel dump with pipe'); + +$node->command_ok( + [ + 'pg_restore', '--verbose', + '--dbname' => $node->connstr($dbname4), + '--format' => 'directory', + '--jobs' => 3, + "--pipe=$pipe_restore", + ], + 'parallel restore with pipe'); done_testing(); diff --git a/src/bin/pg_dump/t/005_pg_dump_filterfile.pl b/src/bin/pg_dump/t/005_pg_dump_filterfile.pl index cecf0442088..f4b1afd8ef6 100644 --- a/src/bin/pg_dump/t/005_pg_dump_filterfile.pl +++ b/src/bin/pg_dump/t/005_pg_dump_filterfile.pl @@ -8,6 +8,11 @@ use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; use Test::More; +# Use perl one-liner as a portable 'cat' replacement for Windows compatibility. +my $perlbin = $^X; +$perlbin =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os; +my $perl_cat = "$perlbin -pe ''"; + my $tempdir = PostgreSQL::Test::Utils::tempdir; my $inputfile; @@ -98,6 +103,19 @@ command_ok( ], "filter file without patterns"); +mkdir "$backupdir/dump_pipe_filter"; + +command_ok( + [ + 'pg_dump', + '--port' => $port, + '--format' => 'directory', + '--pipe' => "$perl_cat > $backupdir/dump_pipe_filter/%f", + '--filter' => "$tempdir/inputfile.txt", + 'postgres' + ], + "filter file without patterns with pipe"); + my $dump = slurp_file($plainfile); like($dump, qr/^CREATE TABLE public\.table_one/m, "table one dumped"); -- 2.54.0.669.g59709faab0-goog