From e3d0f8741158b1b4520dbe753f8e7cd5b996c4fa Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 26 May 2020 14:22:00 -0700
Subject: [PATCH v1] Implementing new 'pg' consolidated metacommand

This implements a new command to consolidate disparate postgres
command line programs into one.
---
 contrib/sepgsql/test_sepgsql                  |   1 +
 contrib/start-scripts/freebsd                 |   2 +-
 contrib/start-scripts/linux                   |   2 +-
 .../start-scripts/macos/postgres-wrapper.sh   |   4 +-
 doc/src/sgml/ref/pgupgrade.sgml               |  22 +-
 src/Makefile.global.in                        |  10 +-
 src/backend/postmaster/postmaster.c           |   7 +-
 src/backend/utils/init/globals.c              |   1 +
 src/backend/utils/init/miscinit.c             |   7 +
 src/backend/utils/misc/pg_config.c            |   3 +-
 src/bin/Makefile                              |   1 +
 src/bin/initdb/Makefile                       |   6 +-
 src/bin/initdb/initdb.c                       |   4 +-
 src/bin/initdb/t/002_redirect.pl              |  17 ++
 src/bin/pg/.gitignore                         |   4 +
 src/bin/pg/Makefile                           |  48 +++++
 src/bin/pg/pg.c                               | 194 ++++++++++++++++++
 src/bin/pg/t/001_basic.pl                     |  31 +++
 src/bin/pg_archivecleanup/Makefile            |   6 +-
 src/bin/pg_archivecleanup/t/001_redirect.pl   |  17 ++
 .../t/010_pg_archivecleanup.pl                |  10 +-
 src/bin/pg_basebackup/Makefile                |  14 +-
 src/bin/pg_basebackup/t/001_redirect.pl       |  17 ++
 src/bin/pg_basebackup/t/010_pg_basebackup.pl  |  72 +++----
 src/bin/pg_basebackup/t/020_pg_receivewal.pl  |  14 +-
 src/bin/pg_basebackup/t/030_pg_recvlogical.pl |   8 +-
 src/bin/pg_checksums/Makefile                 |   6 +-
 src/bin/pg_checksums/t/002_actions.pl         |  38 ++--
 src/bin/pg_checksums/t/003_redirect.pl        |  17 ++
 src/bin/pg_config/Makefile                    |   6 +-
 src/bin/pg_config/pg_config.c                 |  16 +-
 src/bin/pg_config/t/001_pg_config.pl          |  12 +-
 src/bin/pg_controldata/Makefile               |   6 +-
 .../pg_controldata/t/001_pg_controldata.pl    |   8 +-
 src/bin/pg_controldata/t/002_redirect.pl      |  17 ++
 src/bin/pg_ctl/Makefile                       |   6 +-
 src/bin/pg_ctl/pg_ctl.c                       |  12 +-
 src/bin/pg_ctl/t/001_start_stop.pl            |  22 +-
 src/bin/pg_ctl/t/002_status.pl                |  10 +-
 src/bin/pg_ctl/t/003_promote.pl               |  10 +-
 src/bin/pg_ctl/t/005_redirect.pl              |  17 ++
 src/bin/pg_dump/Makefile                      |  10 +-
 src/bin/pg_dump/pg_dumpall.c                  |   4 +-
 src/bin/pg_dump/t/004_redirect.pl             |  17 ++
 src/bin/pg_resetwal/Makefile                  |   6 +-
 src/bin/pg_resetwal/t/003_redirect.pl         |  17 ++
 src/bin/pg_rewind/Makefile                    |   6 +-
 src/bin/pg_rewind/pg_rewind.c                 |  12 +-
 src/bin/pg_rewind/t/007_redirect.pl           |  17 ++
 src/bin/pg_test_fsync/Makefile                |   6 +-
 src/bin/pg_test_timing/Makefile               |   6 +-
 src/bin/pg_upgrade/Makefile                   |   8 +-
 src/bin/pg_upgrade/TESTING                    |   2 +
 src/bin/pg_upgrade/check.c                    |   6 +-
 src/bin/pg_upgrade/controldata.c              |   4 +-
 src/bin/pg_upgrade/dump.c                     |   8 +-
 src/bin/pg_upgrade/exec.c                     | 119 ++++++-----
 src/bin/pg_upgrade/option.c                   |  66 ++++--
 src/bin/pg_upgrade/pg_upgrade.c               |  73 ++++---
 src/bin/pg_upgrade/pg_upgrade.h               |  13 ++
 src/bin/pg_upgrade/server.c                   |   8 +-
 src/bin/pg_upgrade/test.sh                    |  13 +-
 src/bin/pg_verifybackup/Makefile              |   6 +-
 src/bin/pg_verifybackup/pg_verifybackup.c     |   6 +-
 src/bin/pg_verifybackup/t/008_redirect.pl     |  17 ++
 src/bin/pg_waldump/Makefile                   |   6 +-
 src/bin/pg_waldump/t/002_redirect.pl          |  17 ++
 src/bin/pgbench/Makefile                      |   6 +-
 src/bin/pgbench/t/003_redirect.pl             |  17 ++
 src/bin/psql/startup.c                        |   9 +-
 src/bin/scripts/Makefile                      |  20 +-
 src/common/.gitignore                         |   1 +
 src/common/Makefile                           |   8 +-
 src/common/config_info.c                      |  28 ++-
 src/common/exec.c                             | 143 ++++++++++++-
 src/common/mk_exec_relpath.pl                 |  85 ++++++++
 src/include/common/config_info.h              |   1 +
 src/include/miscadmin.h                       |   1 +
 src/include/port.h                            |  20 +-
 src/interfaces/ecpg/test/Makefile             |  10 +-
 src/port/Makefile                             |   1 +
 src/port/path.c                               | 103 ++++++++--
 src/test/isolation/isolation_main.c           |   8 +-
 src/test/regress/pg_regress.c                 |  39 ++--
 src/tools/msvc/Solution.pm                    |   1 +
 src/tools/msvc/vcregress.pl                   |   2 +
 86 files changed, 1336 insertions(+), 365 deletions(-)
 create mode 100644 src/bin/initdb/t/002_redirect.pl
 create mode 100644 src/bin/pg/.gitignore
 create mode 100644 src/bin/pg/Makefile
 create mode 100644 src/bin/pg/pg.c
 create mode 100644 src/bin/pg/t/001_basic.pl
 create mode 100644 src/bin/pg_archivecleanup/t/001_redirect.pl
 create mode 100644 src/bin/pg_basebackup/t/001_redirect.pl
 create mode 100644 src/bin/pg_checksums/t/003_redirect.pl
 create mode 100644 src/bin/pg_controldata/t/002_redirect.pl
 create mode 100644 src/bin/pg_ctl/t/005_redirect.pl
 create mode 100644 src/bin/pg_dump/t/004_redirect.pl
 create mode 100644 src/bin/pg_resetwal/t/003_redirect.pl
 create mode 100644 src/bin/pg_rewind/t/007_redirect.pl
 create mode 100644 src/bin/pg_verifybackup/t/008_redirect.pl
 create mode 100644 src/bin/pg_waldump/t/002_redirect.pl
 create mode 100644 src/bin/pgbench/t/003_redirect.pl
 create mode 100755 src/common/mk_exec_relpath.pl

diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql
index 3a29556d1f..b2dc566994 100755
--- a/contrib/sepgsql/test_sepgsql
+++ b/contrib/sepgsql/test_sepgsql
@@ -13,6 +13,7 @@
 #
 
 PG_BINDIR=`pg_config --bindir`
+PG_LIBEXECDIR=`pg_config --libexecdir`
 
 # we must move to contrib/sepgsql directory to run pg_regress correctly
 cd `dirname $0`
diff --git a/contrib/start-scripts/freebsd b/contrib/start-scripts/freebsd
index 3323237a54..febb8862e9 100644
--- a/contrib/start-scripts/freebsd
+++ b/contrib/start-scripts/freebsd
@@ -32,7 +32,7 @@ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
 DAEMON="$prefix/bin/postmaster"
 
 # What to use to shut down the postmaster
-PGCTL="$prefix/bin/pg_ctl"
+PGCTL="$prefix/libexec/pg_ctl"
 
 # Only start if we can find the postmaster.
 test -x $DAEMON ||
diff --git a/contrib/start-scripts/linux b/contrib/start-scripts/linux
index a7757162fc..9425ba2453 100644
--- a/contrib/start-scripts/linux
+++ b/contrib/start-scripts/linux
@@ -64,7 +64,7 @@ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
 DAEMON="$prefix/bin/postmaster"
 
 # What to use to shut down the postmaster
-PGCTL="$prefix/bin/pg_ctl"
+PGCTL="$prefix/libexec/pg_ctl"
 
 set -e
 
diff --git a/contrib/start-scripts/macos/postgres-wrapper.sh b/contrib/start-scripts/macos/postgres-wrapper.sh
index 3a4ebdaf0f..2be058480e 100644
--- a/contrib/start-scripts/macos/postgres-wrapper.sh
+++ b/contrib/start-scripts/macos/postgres-wrapper.sh
@@ -4,8 +4,10 @@
 
 # edit these as needed:
 
-# directory containing postgres executable:
+# directory containing postgres user executable:
 PGBINDIR="/usr/local/pgsql/bin"
+# directory containing postgres program executables:
+PGLIBEXECDIR="/usr/local/pgsql/libexec"
 # data directory:
 PGDATA="/usr/local/pgsql/data"
 # file to receive postmaster's initial log messages:
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 905167690b..5c2a6fa242 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -26,6 +26,10 @@ PostgreSQL documentation
    <arg choice="plain"><replaceable>oldbindir</replaceable></arg>
    <arg choice="plain"><option>-B</option></arg>
    <arg choice="plain"><replaceable>newbindir</replaceable></arg>
+   <arg choice="plain"><option>-l</option></arg>
+   <arg choice="plain"><replaceable>oldlibexecdir</replaceable></arg>
+   <arg choice="plain"><option>-L</option></arg>
+   <arg choice="plain"><replaceable>newlibexecdir</replaceable></arg>
    <arg choice="plain"><option>-d</option></arg>
    <arg choice="plain"><replaceable>oldconfigdir</replaceable></arg>
    <arg choice="plain"><option>-D</option></arg>
@@ -92,7 +96,6 @@ PostgreSQL documentation
       <term><option>-B</option> <replaceable>bindir</replaceable></term>
       <term><option>--new-bindir=</option><replaceable>bindir</replaceable></term>
       <listitem><para>the new PostgreSQL executable directory;
-      default is the directory where <application>pg_upgrade</application> resides;
       environment variable <envar>PGBINNEW</envar></para></listitem>
      </varlistentry>
 
@@ -130,6 +133,21 @@ PostgreSQL documentation
       cluster</para></listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-l</option> <replaceable>libexecdir</replaceable></term>
+      <term><option>--old-libexecdir=</option><replaceable>libexecdir</replaceable></term>
+      <listitem><para>the old PostgreSQL command directory;
+      environment variable <envar>PGLIBEXECOLD</envar></para></listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-L</option> <replaceable>libexecdir</replaceable></term>
+      <term><option>--new-libexecdir=</option><replaceable>libexecdir</replaceable></term>
+      <listitem><para>the new PostgreSQL command directory;
+      default is the directory where <application>pg_upgrade</application> resides;
+      environment variable <envar>PGLIBEXECNEW</envar></para></listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-o</option> <replaceable class="parameter">options</replaceable></term>
       <term><option>--old-options</option> <replaceable class="parameter">options</replaceable></term>
@@ -418,6 +436,8 @@ pg_upgrade.exe
         --new-datadir "C:/Program Files/PostgreSQL/&majorversion;/data"
         --old-bindir "C:/Program Files/PostgreSQL/9.6/bin"
         --new-bindir "C:/Program Files/PostgreSQL/&majorversion;/bin"
+        --old-libexecdir "C:/Program Files/PostgreSQL/9.6/libexec"
+        --new-libexecdir "C:/Program Files/PostgreSQL/&majorversion;/libexec"
 </programlisting>
 
      Once started, <command>pg_upgrade</command> will verify the two clusters are compatible
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..b0692f40fb 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -99,6 +99,7 @@ exec_prefix := @exec_prefix@
 datarootdir := @datarootdir@
 
 bindir := @bindir@
+libexecdir := @libexecdir@
 
 datadir := @datadir@
 ifeq "$(findstring pgsql, $(datadir))" ""
@@ -153,6 +154,7 @@ PG_CONFIG = pg_config
 endif
 
 bindir := $(shell $(PG_CONFIG) --bindir)
+libexecdir := $(shell $(PG_CONFIG) --libexecdir)
 datadir := $(shell $(PG_CONFIG) --sharedir)
 sysconfdir := $(shell $(PG_CONFIG) --sysconfdir)
 libdir := $(shell $(PG_CONFIG) --libdir)
@@ -438,7 +440,7 @@ ld_library_path_var = LD_LIBRARY_PATH
 # need something more here. If not defined then the expansion does
 # nothing.
 with_temp_install = \
-	PATH="$(abs_top_builddir)/tmp_install$(bindir):$$PATH" \
+	PATH="$(abs_top_builddir)/tmp_install$(bindir):$(abs_top_builddir)/tmp_install$(libexecdir):$$PATH" \
 	$(call add_to_path,$(strip $(ld_library_path_var)),$(abs_top_builddir)/tmp_install$(libdir)) \
 	$(with_temp_install_extra)
 
@@ -447,7 +449,7 @@ ifeq ($(enable_tap_tests),yes)
 define prove_installcheck
 rm -rf '$(CURDIR)'/tmp_check
 $(MKDIR_P) '$(CURDIR)'/tmp_check
-cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
+cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$(libexecdir):$$PATH" PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) $(if $(PROVE_TESTS),$(PROVE_TESTS),t/*.pl)
 endef
 
 define prove_check
@@ -644,12 +646,14 @@ pg_regress_check = \
     $(top_builddir)/src/test/regress/pg_regress \
     --temp-instance=./tmp_check \
     --inputdir=$(srcdir) \
+    --libexecdir= \
     --bindir= \
     $(TEMP_CONF) \
     $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS)
 pg_regress_installcheck = \
     $(top_builddir)/src/test/regress/pg_regress \
     --inputdir=$(srcdir) \
+    --libexecdir='$(libexecdir)' \
     --bindir='$(bindir)' \
     $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS)
 
@@ -658,12 +662,14 @@ pg_isolation_regress_check = \
     $(top_builddir)/src/test/isolation/pg_isolation_regress \
     --temp-instance=./tmp_check_iso \
     --inputdir=$(srcdir) --outputdir=output_iso \
+    --libexecdir= \
     --bindir= \
     $(TEMP_CONF) \
     $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS)
 pg_isolation_regress_installcheck = \
     $(top_builddir)/src/test/isolation/pg_isolation_regress \
     --inputdir=$(srcdir) --outputdir=output_iso \
+    --libexecdir='$(libexecdir)' \
     --bindir='$(bindir)' \
     $(pg_regress_locale_flags) $(EXTRA_REGRESS_OPTS)
 
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 160afe9f39..5f7633df94 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1468,10 +1468,13 @@ getInstallationPaths(const char *argv0)
 	if (find_my_exec(argv0, my_exec_path) < 0)
 		elog(FATAL, "%s: could not locate my own executable path", argv0);
 
+	if (find_my_rootdir(argv0, my_rootdir) < 0)
+		elog(FATAL, "%s: could not locate my own root directory", argv0);
+
 #ifdef EXEC_BACKEND
 	/* Locate executable backend before we change working directory */
-	if (find_other_exec(argv0, "postgres", PG_BACKEND_VERSIONSTR,
-						postgres_exec_path) < 0)
+	if (find_other_cmd(argv0, "postgres", PG_BACKEND_VERSIONSTR,
+					   postgres_exec_path) < 0)
 		ereport(FATAL,
 				(errmsg("%s: could not locate matching postgres executable",
 						argv0)));
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index eb19644419..3b0a2e84a6 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -70,6 +70,7 @@ int			data_directory_mode = PG_DIR_MODE_OWNER;
 char		OutputFileName[MAXPGPATH];	/* debugging output file */
 
 char		my_exec_path[MAXPGPATH];	/* full path to my executable */
+char		my_rootdir[MAXPGPATH];		/* full path to postgres root directory */
 char		pkglib_path[MAXPGPATH]; /* full path to lib directory */
 
 #ifdef EXEC_BACKEND
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index cca9704d2d..01e82f227f 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -161,6 +161,13 @@ InitStandaloneProcess(const char *argv0)
 				 argv0);
 	}
 
+	if (my_rootdir[0] == '\0')
+	{
+		if (find_my_rootdir(argv0, my_rootdir) < 0)
+			elog(FATAL, "%s: could not locate my own installation root",
+				 argv0);
+	}
+
 	if (pkglib_path[0] == '\0')
 		get_pkglib_path(my_exec_path, pkglib_path);
 }
diff --git a/src/backend/utils/misc/pg_config.c b/src/backend/utils/misc/pg_config.c
index 7a79cbff92..e081eacce7 100644
--- a/src/backend/utils/misc/pg_config.c
+++ b/src/backend/utils/misc/pg_config.c
@@ -69,7 +69,8 @@ pg_config(PG_FUNCTION_ARGS)
 	/* initialize our tuplestore */
 	tupstore = tuplestore_begin_heap(true, false, work_mem);
 
-	configdata = get_configdata(my_exec_path, &configdata_len);
+	configdata = get_configdata(my_exec_path, my_rootdir,
+								&configdata_len);
 	for (i = 0; i < configdata_len; i++)
 	{
 		values[0] = configdata[i].name;
diff --git a/src/bin/Makefile b/src/bin/Makefile
index 8b870357a1..ec0534422e 100644
--- a/src/bin/Makefile
+++ b/src/bin/Makefile
@@ -15,6 +15,7 @@ include $(top_builddir)/src/Makefile.global
 
 SUBDIRS = \
 	initdb \
+	pg \
 	pg_archivecleanup \
 	pg_basebackup \
 	pg_checksums \
diff --git a/src/bin/initdb/Makefile b/src/bin/initdb/Makefile
index 7e23754780..409247f22f 100644
--- a/src/bin/initdb/Makefile
+++ b/src/bin/initdb/Makefile
@@ -47,13 +47,13 @@ localtime.c: % : $(top_srcdir)/src/timezone/%
 	rm -f $@ && $(LN_S) $< .
 
 install: all installdirs
-	$(INSTALL_PROGRAM) initdb$(X) '$(DESTDIR)$(bindir)/initdb$(X)'
+	$(INSTALL_PROGRAM) initdb$(X) '$(DESTDIR)$(libexecdir)/initdb$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/initdb$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/initdb$(X)'
 
 clean distclean maintainer-clean:
 	rm -f initdb$(X) $(OBJS) localtime.c
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 67021a6dc1..475b72e260 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2422,8 +2422,8 @@ setup_bin_paths(const char *argv0)
 {
 	int			ret;
 
-	if ((ret = find_other_exec(argv0, "postgres", PG_BACKEND_VERSIONSTR,
-							   backend_exec)) < 0)
+	if ((ret = find_other_cmd(argv0, "postgres", PG_BACKEND_VERSIONSTR,
+							  backend_exec)) < 0)
 	{
 		char		full_path[MAXPGPATH];
 
diff --git a/src/bin/initdb/t/002_redirect.pl b/src/bin/initdb/t/002_redirect.pl
new file mode 100644
index 0000000000..5a2176b32b
--- /dev/null
+++ b/src/bin/initdb/t/002_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'initdb'],
+	qr/error: no data directory specified/,
+	'pg initdb fails');
+
+command_ok(
+	['pg', 'initdb', '--version'],
+	'pg initdb version ok');
+
+command_ok(
+	['pg', 'initdb', '--help'],
+	'pg initdb help ok');
diff --git a/src/bin/pg/.gitignore b/src/bin/pg/.gitignore
new file mode 100644
index 0000000000..f21dfa43a0
--- /dev/null
+++ b/src/bin/pg/.gitignore
@@ -0,0 +1,4 @@
+/pg
+/pg_libdir_relpath.h
+
+/tmp_check/
diff --git a/src/bin/pg/Makefile b/src/bin/pg/Makefile
new file mode 100644
index 0000000000..5ac883ce10
--- /dev/null
+++ b/src/bin/pg/Makefile
@@ -0,0 +1,48 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/bin/pg
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/bin/pg/Makefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "pg - consolidated PostgreSQL command line interface client"
+PGAPPICON=win32
+
+subdir = src/bin/pg
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+
+OBJS = \
+	$(WIN32RES) \
+	pg.o
+
+all: pg
+
+pg: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
+	$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+install: all installdirs
+	$(INSTALL_PROGRAM) pg$(X) '$(DESTDIR)$(bindir)'/pg$(X)
+
+installdirs:
+	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+check:
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
+
+uninstall:
+	rm -f $(addprefix '$(DESTDIR)$(bindir)'/, pg$(X))
+
+clean distclean maintainer-clean:
+	rm -f pg$(X) $(OBJS) pg.o
+	rm -rf tmp_check
diff --git a/src/bin/pg/pg.c b/src/bin/pg/pg.c
new file mode 100644
index 0000000000..2341843fcd
--- /dev/null
+++ b/src/bin/pg/pg.c
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg --- consolidated PostgreSQL command line interface client
+ *
+ * This code is released under the terms of the PostgreSQL License.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/pg/pg.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <stdio.h>
+
+#include "common/logging.h"
+#include "lib/stringinfo.h"
+#include "port.h"
+
+/* internal vars */
+static const char *progname;
+
+/* Map user supplied command names to installed executable names. */
+typedef struct NameMap {
+	const char *cmdname;
+	const char *executable;
+} NameMap;
+
+/*
+ * The standard format is that "name" => "pg_name", but this is not so
+ * for some executables, and for compatibility we don't want to change
+ * the executable name.  Instead, nonstandard names are listed here.
+ */
+const NameMap name_map[] = {
+	{ .cmdname = "clusterdb", .executable = "clusterdb" },
+	{ .cmdname = "createdb", .executable = "createdb" },
+	{ .cmdname = "createuser", .executable = "createuser" },
+	{ .cmdname = "dropdb", .executable = "dropdb" },
+	{ .cmdname = "dropuser", .executable = "dropuser" },
+	{ .cmdname = "initdb", .executable = "initdb" },
+	{ .cmdname = "bench", .executable = "pgbench" },
+	{ .cmdname = "reindexdb", .executable = "reindexdb" },
+	{ .cmdname = "vacuumdb", .executable = "vacuumdb" },
+	{ .cmdname = NULL }
+};
+
+static char *executable_for_command(const char *cmdname);
+static void usage(const char *progname);
+
+int
+main(int argc, char *argv[])
+{
+	setvbuf(stdout, NULL, _IONBF, 0);
+	pg_logging_init(argv[0]);
+	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg"));
+	progname = get_progname(argv[0]);
+
+	/*
+	 * Most options are handled in the various sub-command executables, not
+	 * here.  The only options checked for here are ones that `pg' will handle
+	 * even when there is no sub-command to which the arguments will be handed
+	 * off.  For now, this is just help and version information.
+	 */
+	if (argc > 1)
+	{
+		char	   *found_path;
+		char	   *executable;
+		char		cmd_version[MAXPGPATH + 512];
+
+		found_path = pg_malloc(MAXPGPATH);
+
+		/* Handle recognized options */
+		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+		{
+			usage(progname);
+			exit(0);
+		}
+		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+		{
+			puts("pg (PostgreSQL) " PG_VERSION);
+			exit(0);
+		}
+
+		/* If it is an unrecognized option, return an appropriate error */
+		if (argv[1][0] == '-')
+		{
+			fprintf(stderr,_("%s: error: invalid option: %s\n"), progname, argv[1]);
+			exit(1);
+		}
+
+		/* Otherwise, try to interpret the argument as a command name */
+		executable = executable_for_command(argv[1]);
+		snprintf(cmd_version, sizeof(cmd_version), "%s (PostgreSQL) %s\n", executable, PG_VERSION);
+		if (find_other_cmd(argv[0], executable, cmd_version, found_path) == 0)
+		{
+			StringInfoData		cmd;
+			int					argidx;
+			int					ret;
+
+			/*
+			 * Construct a command string to hand to system() from argv[1..n], noting that
+			 * argv[1] is the command name and needs to be rewritten as an absolute path
+			 * to the appropriate executable.  For argv[2..n], we do no processing, but
+			 * need to be careful to handle any escaping and quoting rules for the system.
+			 */
+			initStringInfo(&cmd);
+			appendStringInfo(&cmd, "%s", found_path);
+			for (argidx = 2; argidx < argc; argidx++)
+			{
+				/* XXX: Is this escaping sufficient? */
+				char *arg = escape_single_quotes_ascii(argv[argidx]);
+				appendStringInfo(&cmd, " %s", arg);
+			}
+
+			ret = system(cmd.data);
+			if (ret)
+			{
+				fprintf(stderr,_("%s: command failed: %s\n"), progname, cmd.data);
+				exit(ret >> 8);
+			}
+			exit(0);
+		}
+
+		/* It's either an unrecognized command or garbage.  Charitably assume it is a command */
+		fprintf(stderr,_("%s: error: unrecognized command: %s\n"), progname, argv[1]);
+		exit(1);
+	}
+
+	fprintf(stderr,_("%s: missing command\n"), progname);
+	exit(1);
+}
+
+static
+char *executable_for_command(const char *cmdname)
+{
+	int i;
+
+	/* Check for nonstandard executable names */
+	for (i = 0; name_map[i].cmdname; i++)
+	{
+		if (strcmp(cmdname, name_map[i].cmdname) == 0)
+			return pstrdup(name_map[i].executable);
+	}
+
+	/* Otherwise, return standard executable name derived from cmdname */
+	return psprintf("pg_%s", cmdname);
+}
+
+/*
+ * print help text
+ */
+static void
+usage(const char *progname)
+{
+	printf(_("%s is the consolidated PostgreSQL command line interface client.\n\n"), progname);
+	printf(_("Usage:\n"));
+	printf(_("  %s [OPTION]... [COMMAND] [COMMAND OPTION]...\n"), progname);
+	printf(_("\nOptions:\n"));
+	printf(_("  -V, --version             output version information, then exit\n"));
+	printf(_("  -?, --help                show this help, then exit\n"));
+	printf(_("\nCommands:\n"));
+	printf(_("  archivecleanup          remove older WAL files from PostgreSQL archives\n"));
+	printf(_("  basebackup              take a base backup of a running PostgreSQL server\n"));
+	printf(_("  bench                   run benchmark tests against a PostgreSQL server\n"));
+	printf(_("  checksums               enable, disable, or verify data checksums in a PostgreSQL database cluster\n"));
+	printf(_("  clusterdb               cluster all previously clustered tables in a database\n"));
+	printf(_("  config                  show information about the installed version of PostgreSQL\n"));
+	printf(_("  controldata             display control information of a PostgreSQL database cluster\n"));
+	printf(_("  createdb                create a PostgreSQL database\n"));
+	printf(_("  createuser              create a new PostgreSQL role\n"));
+	printf(_("  ctl                     initialize, start, stop, or control a PostgreSQL server\n"));
+	printf(_("  dropdb                  remove a PostgreSQL database\n"));
+	printf(_("  dropuser                remove a PostgreSQL role\n"));
+	printf(_("  dump                    dump a database as a text file or to other formats\n"));
+	printf(_("  dumpall                 extract a PostgreSQL database cluster into an SQL script file\n"));
+	printf(_("  initdb                  initialize a PostgreSQL database cluster\n"));
+	printf(_("  isready                 issue a connection check to a PostgreSQL database\n"));
+	printf(_("  receivewal              receive PostgreSQL streaming write-ahead logs\n"));
+	printf(_("  recvlogical             control PostgreSQL logical decoding streams\n"));
+	printf(_("  reindexdb               reindex a PostgreSQL database\n"));
+	printf(_("  resetwal                reset the PostgreSQL write-ahead log\n"));
+	printf(_("  restore                 restore a PostgreSQL database from an archive created by pg_dump\n"));
+	printf(_("  rewind                  resynchronize a PostgreSQL cluster with another copy of the cluster\n"));
+	printf(_("  test_fsync              test all supported fsync() methods\n"));
+	printf(_("  test_timing             test overhead of timing calls and their monotonicity\n"));
+	printf(_("  upgrade                 upgrade a PostgreSQL cluster to a different major version\n"));
+	printf(_("  vacuumdb                clean and analyze a PostgreSQL database\n"));
+	printf(_("  verifybackup            verify a backup against the backup manifest\n"));
+	printf(_("  waldump                 decode and display PostgreSQL write-ahead logs for debugging\n"));
+	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
diff --git a/src/bin/pg/t/001_basic.pl b/src/bin/pg/t/001_basic.pl
new file mode 100644
index 0000000000..d349aa074e
--- /dev/null
+++ b/src/bin/pg/t/001_basic.pl
@@ -0,0 +1,31 @@
+use strict;
+use warnings;
+
+use TestLib;
+use Test::More tests => 13;
+
+program_help_ok('pg');
+program_version_ok('pg');
+program_options_handling_ok('pg');
+
+command_fails_like(
+	[ 'pg', '-a' ],
+	qr/\Qpg: error: invalid option: -a\E/,
+	'pg: invalid command-line arguments');
+
+command_ok(
+	['pg', '--version'],
+	'pg version ok');
+
+command_ok(
+	['pg', '--help'],
+	'pg help ok');
+
+# Checks of various commands, such as 'pg initdb', are implemented
+# in tests named /.*_redirect.pl/ in the test directory of the
+# command in question, so we do not need to duplicate that here.
+# But to help developers who change pg.c and run 'make check' in
+# the pg directory, it helps to have at least one example of that
+# for smoke testing.
+
+command_ok([ 'pg', 'initdb', '--version' ], 'pg redirection');
diff --git a/src/bin/pg_archivecleanup/Makefile b/src/bin/pg_archivecleanup/Makefile
index 49935d6dce..00888294d9 100644
--- a/src/bin/pg_archivecleanup/Makefile
+++ b/src/bin/pg_archivecleanup/Makefile
@@ -17,13 +17,13 @@ pg_archivecleanup: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_archivecleanup$(X) '$(DESTDIR)$(bindir)/pg_archivecleanup$(X)'
+	$(INSTALL_PROGRAM) pg_archivecleanup$(X) '$(DESTDIR)$(libexecdir)/pg_archivecleanup$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_archivecleanup$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_archivecleanup$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_archivecleanup$(X) $(OBJS)
diff --git a/src/bin/pg_archivecleanup/t/001_redirect.pl b/src/bin/pg_archivecleanup/t/001_redirect.pl
new file mode 100644
index 0000000000..c68aa89c58
--- /dev/null
+++ b/src/bin/pg_archivecleanup/t/001_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'archivecleanup'],
+	qr/error: must specify archive location/,
+	'pg pg_archivecleanup fails');
+
+command_ok(
+	['pg', 'archivecleanup', '--version'],
+	'pg pg_archivecleanup version ok');
+
+command_ok(
+	['pg', 'archivecleanup', '--help'],
+	'pg pg_archivecleanup help ok');
diff --git a/src/bin/pg_archivecleanup/t/010_pg_archivecleanup.pl b/src/bin/pg_archivecleanup/t/010_pg_archivecleanup.pl
index 22782d3042..15a14efb1e 100644
--- a/src/bin/pg_archivecleanup/t/010_pg_archivecleanup.pl
+++ b/src/bin/pg_archivecleanup/t/010_pg_archivecleanup.pl
@@ -27,27 +27,27 @@ sub create_files
 create_files();
 
 command_fails_like(
-	['pg_archivecleanup'],
+	['pg', 'archivecleanup'],
 	qr/must specify archive location/,
 	'fails if archive location is not specified');
 
 command_fails_like(
-	[ 'pg_archivecleanup', $tempdir ],
+	[ 'pg', 'archivecleanup', $tempdir ],
 	qr/must specify oldest kept WAL file/,
 	'fails if oldest kept WAL file name is not specified');
 
 command_fails_like(
-	[ 'pg_archivecleanup', 'notexist', 'foo' ],
+	[ 'pg', 'archivecleanup', 'notexist', 'foo' ],
 	qr/archive location .* does not exist/,
 	'fails if archive location does not exist');
 
 command_fails_like(
-	[ 'pg_archivecleanup', $tempdir, 'foo', 'bar' ],
+	[ 'pg', 'archivecleanup', $tempdir, 'foo', 'bar' ],
 	qr/too many command-line arguments/,
 	'fails with too many command-line arguments');
 
 command_fails_like(
-	[ 'pg_archivecleanup', $tempdir, 'foo' ],
+	[ 'pg', 'archivecleanup', $tempdir, 'foo' ],
 	qr/invalid file name argument/,
 	'fails with invalid restart file name');
 
diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile
index 988007c6fd..65a6739cfe 100644
--- a/src/bin/pg_basebackup/Makefile
+++ b/src/bin/pg_basebackup/Makefile
@@ -39,17 +39,17 @@ pg_recvlogical: pg_recvlogical.o $(OBJS) | submake-libpq submake-libpgport subma
 	$(CC) $(CFLAGS) pg_recvlogical.o $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_basebackup$(X) '$(DESTDIR)$(bindir)/pg_basebackup$(X)'
-	$(INSTALL_PROGRAM) pg_receivewal$(X) '$(DESTDIR)$(bindir)/pg_receivewal$(X)'
-	$(INSTALL_PROGRAM) pg_recvlogical$(X) '$(DESTDIR)$(bindir)/pg_recvlogical$(X)'
+	$(INSTALL_PROGRAM) pg_basebackup$(X) '$(DESTDIR)$(libexecdir)/pg_basebackup$(X)'
+	$(INSTALL_PROGRAM) pg_receivewal$(X) '$(DESTDIR)$(libexecdir)/pg_receivewal$(X)'
+	$(INSTALL_PROGRAM) pg_recvlogical$(X) '$(DESTDIR)$(libexecdir)/pg_recvlogical$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_basebackup$(X)'
-	rm -f '$(DESTDIR)$(bindir)/pg_receivewal$(X)'
-	rm -f '$(DESTDIR)$(bindir)/pg_recvlogical$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_basebackup$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_receivewal$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_recvlogical$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_basebackup$(X) pg_receivewal$(X) pg_recvlogical$(X) \
diff --git a/src/bin/pg_basebackup/t/001_redirect.pl b/src/bin/pg_basebackup/t/001_redirect.pl
new file mode 100644
index 0000000000..dbdbb5c849
--- /dev/null
+++ b/src/bin/pg_basebackup/t/001_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'basebackup'],
+	qr/error: no target directory specified/,
+	'pg pg_basebackup fails');
+
+command_ok(
+	['pg', 'basebackup', '--version'],
+	'pg pg_basebackup version ok');
+
+command_ok(
+	['pg', 'basebackup', '--help'],
+	'pg pg_basebackup help ok');
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 208df557b8..e189abff35 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -24,7 +24,7 @@ $node->init(extra => ['--data-checksums']);
 $node->start;
 my $pgdata = $node->data_dir;
 
-$node->command_fails(['pg_basebackup'],
+$node->command_fails(['pg', 'basebackup'],
 	'pg_basebackup needs target directory specified');
 
 # Some Windows ANSI code pages may reject this filename, in which case we
@@ -39,7 +39,7 @@ $node->set_replication_conf();
 system_or_bail 'pg_ctl', '-D', $pgdata, 'reload';
 
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backup" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup" ],
 	'pg_basebackup fails because of WAL configuration');
 
 ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
@@ -50,7 +50,7 @@ mkdir("$tempdir/backup")
   or BAIL_OUT("unable to create $tempdir/backup");
 append_to_file("$tempdir/backup/dir-not-empty.txt", "Some data");
 
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
+$node->command_fails([ 'pg', 'basebackup', '-D', "$tempdir/backup", '-n' ],
 	'failing run with no-clean option');
 
 ok(-d "$tempdir/backup", 'backup directory was created and left behind');
@@ -101,7 +101,7 @@ foreach my $filename (@tempRelationFiles)
 }
 
 # Run base backup.
-$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
+$node->command_ok([ 'pg', 'basebackup', '-D', "$tempdir/backup", '-X', 'none' ],
 	'pg_basebackup runs');
 ok(-f "$tempdir/backup/PG_VERSION",      'backup was created');
 ok(-f "$tempdir/backup/backup_manifest", 'backup manifest included');
@@ -161,7 +161,7 @@ rmtree("$tempdir/backup");
 
 $node->command_ok(
 	[
-		'pg_basebackup',    '-D',
+		'pg', 'basebackup',    '-D',
 		"$tempdir/backup2", '--no-manifest',
 		'--waldir',         "$tempdir/xlog2"
 	],
@@ -172,31 +172,31 @@ ok(-d "$tempdir/xlog2/",                   'xlog directory was created');
 rmtree("$tempdir/backup2");
 rmtree("$tempdir/xlog2");
 
-$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup", '-Ft' ],
+$node->command_ok([ 'pg', 'basebackup', '-D', "$tempdir/tarbackup", '-Ft' ],
 	'tar format');
 ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
 rmtree("$tempdir/tarbackup");
 
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
 	'-T with empty old directory fails');
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
 	'-T with empty new directory fails');
 $node->command_fails(
 	[
-		'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
+		'pg', 'basebackup', '-D', "$tempdir/backup_foo", '-Fp',
 		"-T/foo=/bar=/baz"
 	],
 	'-T with multiple = fails');
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo=/bar" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo=/bar" ],
 	'-T with old directory not absolute fails');
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=bar" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=bar" ],
 	'-T with new directory not absolute fails');
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo" ],
 	'-T with invalid format fails');
 
 # Tar format doesn't support filenames longer than 100 bytes.
@@ -207,7 +207,7 @@ open my $file, '>', "$superlongpath"
   or die "unable to create file $superlongpath";
 close $file;
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/tarbackup_l1", '-Ft' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/tarbackup_l1", '-Ft' ],
 	'pg_basebackup tar with long name fails');
 unlink "$pgdata/$superlongname";
 
@@ -245,7 +245,7 @@ SKIP:
 		"CREATE TABLESPACE tblspc1 LOCATION '$shorter_tempdir/tblspc1';");
 	$node->safe_psql('postgres',
 		"CREATE TABLE test1 (a int) TABLESPACE tblspc1;");
-	$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup2", '-Ft' ],
+	$node->command_ok([ 'pg', 'basebackup', '-D', "$tempdir/tarbackup2", '-Ft' ],
 		'tar format with tablespaces');
 	ok(-f "$tempdir/tarbackup2/base.tar", 'backup tar was created');
 	my @tblspc_tars = glob "$tempdir/tarbackup2/[0-9]*.tar";
@@ -282,12 +282,12 @@ SKIP:
 	}
 
 	$node->command_fails(
-		[ 'pg_basebackup', '-D', "$tempdir/backup1", '-Fp' ],
+		[ 'pg', 'basebackup', '-D', "$tempdir/backup1", '-Fp' ],
 		'plain format with tablespaces fails without tablespace mapping');
 
 	$node->command_ok(
 		[
-			'pg_basebackup', '-D', "$tempdir/backup1", '-Fp',
+			'pg', 'basebackup', '-D', "$tempdir/backup1", '-Fp',
 			"-T$shorter_tempdir/tblspc1=$tempdir/tbackup/tblspc1"
 		],
 		'plain format with tablespaces succeeds with tablespace mapping');
@@ -340,7 +340,7 @@ SKIP:
 		"CREATE TABLESPACE tblspc2 LOCATION '$shorter_tempdir/tbl=spc2';");
 	$node->command_ok(
 		[
-			'pg_basebackup', '-D', "$tempdir/backup3", '-Fp',
+			'pg', 'basebackup', '-D', "$tempdir/backup3", '-Fp',
 			"-T$shorter_tempdir/tbl\\=spc2=$tempdir/tbackup/tbl\\=spc2"
 		],
 		'mapping tablespace with = sign in path');
@@ -353,13 +353,13 @@ SKIP:
 	$node->safe_psql('postgres',
 		"CREATE TABLESPACE tblspc3 LOCATION '$tempdir/$superlongname';");
 	$node->command_ok(
-		[ 'pg_basebackup', '-D', "$tempdir/tarbackup_l3", '-Ft' ],
+		[ 'pg', 'basebackup', '-D', "$tempdir/tarbackup_l3", '-Ft' ],
 		'pg_basebackup tar with long symlink target');
 	$node->safe_psql('postgres', "DROP TABLESPACE tblspc3;");
 	rmtree("$tempdir/tarbackup_l3");
 }
 
-$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backupR", '-R' ],
+$node->command_ok([ 'pg', 'basebackup', '-D', "$tempdir/backupR", '-R' ],
 	'pg_basebackup -R runs');
 ok(-f "$tempdir/backupR/postgresql.auto.conf", 'postgresql.auto.conf exists');
 ok(-f "$tempdir/backupR/standby.signal",       'standby.signal was created');
@@ -373,32 +373,32 @@ like(
 	'postgresql.auto.conf sets primary_conninfo');
 
 $node->command_ok(
-	[ 'pg_basebackup', '-D', "$tempdir/backupxd" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backupxd" ],
 	'pg_basebackup runs in default xlog mode');
 ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxd/pg_wal")),
 	'WAL files copied');
 rmtree("$tempdir/backupxd");
 
 $node->command_ok(
-	[ 'pg_basebackup', '-D', "$tempdir/backupxf", '-X', 'fetch' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backupxf", '-X', 'fetch' ],
 	'pg_basebackup -X fetch runs');
 ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxf/pg_wal")),
 	'WAL files copied');
 rmtree("$tempdir/backupxf");
 $node->command_ok(
-	[ 'pg_basebackup', '-D', "$tempdir/backupxs", '-X', 'stream' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backupxs", '-X', 'stream' ],
 	'pg_basebackup -X stream runs');
 ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxs/pg_wal")),
 	'WAL files copied');
 rmtree("$tempdir/backupxs");
 $node->command_ok(
-	[ 'pg_basebackup', '-D', "$tempdir/backupxst", '-X', 'stream', '-Ft' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backupxst", '-X', 'stream', '-Ft' ],
 	'pg_basebackup -X stream runs in tar mode');
 ok(-f "$tempdir/backupxst/pg_wal.tar", "tar file was created");
 rmtree("$tempdir/backupxst");
 $node->command_ok(
 	[
-		'pg_basebackup',         '-D',
+		'pg', 'basebackup',      '-D',
 		"$tempdir/backupnoslot", '-X',
 		'stream',                '--no-slot'
 	],
@@ -407,7 +407,7 @@ rmtree("$tempdir/backupnoslot");
 
 $node->command_fails(
 	[
-		'pg_basebackup',             '-D',
+		'pg', 'basebackup',          '-D',
 		"$tempdir/backupxs_sl_fail", '-X',
 		'stream',                    '-S',
 		'slot0'
@@ -415,12 +415,12 @@ $node->command_fails(
 	'pg_basebackup fails with nonexistent replication slot');
 
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backupxs_slot", '-C' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backupxs_slot", '-C' ],
 	'pg_basebackup -C fails without slot name');
 
 $node->command_fails(
 	[
-		'pg_basebackup',          '-D',
+		'pg', 'basebackup',       '-D',
 		"$tempdir/backupxs_slot", '-C',
 		'-S',                     'slot0',
 		'--no-slot'
@@ -428,7 +428,7 @@ $node->command_fails(
 	'pg_basebackup fails with -C -S --no-slot');
 
 $node->command_ok(
-	[ 'pg_basebackup', '-D', "$tempdir/backupxs_slot", '-C', '-S', 'slot0' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backupxs_slot", '-C', '-S', 'slot0' ],
 	'pg_basebackup -C runs');
 rmtree("$tempdir/backupxs_slot");
 
@@ -447,7 +447,7 @@ isnt(
 	'restart LSN of new slot is not null');
 
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/backupxs_slot1", '-C', '-S', 'slot0' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backupxs_slot1", '-C', '-S', 'slot0' ],
 	'pg_basebackup fails with -C -S and a previously existing slot');
 
 $node->safe_psql('postgres',
@@ -457,11 +457,11 @@ my $lsn = $node->safe_psql('postgres',
 );
 is($lsn, '', 'restart LSN of new slot is null');
 $node->command_fails(
-	[ 'pg_basebackup', '-D', "$tempdir/fail", '-S', 'slot1', '-X', 'none' ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/fail", '-S', 'slot1', '-X', 'none' ],
 	'pg_basebackup with replication slot fails without WAL streaming');
 $node->command_ok(
 	[
-		'pg_basebackup', '-D', "$tempdir/backupxs_sl", '-X',
+		'pg', 'basebackup', '-D', "$tempdir/backupxs_sl", '-X',
 		'stream',        '-S', 'slot1'
 	],
 	'pg_basebackup -X stream with replication slot runs');
@@ -473,7 +473,7 @@ rmtree("$tempdir/backupxs_sl");
 
 $node->command_ok(
 	[
-		'pg_basebackup', '-D', "$tempdir/backupxs_sl_R", '-X',
+		'pg', 'basebackup', '-D', "$tempdir/backupxs_sl_R", '-X',
 		'stream',        '-S', 'slot1',                  '-R'
 	],
 	'pg_basebackup with replication slot and -R runs');
@@ -507,7 +507,7 @@ close $file;
 system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
 
 $node->command_checks_all(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_corrupt" ],
 	1,
 	[qr{^$}],
 	[qr/^WARNING.*checksum verification failed/s],
@@ -527,7 +527,7 @@ close $file;
 system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
 
 $node->command_checks_all(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_corrupt2" ],
 	1,
 	[qr{^$}],
 	[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
@@ -543,7 +543,7 @@ close $file;
 system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
 
 $node->command_checks_all(
-	[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
+	[ 'pg', 'basebackup', '-D', "$tempdir/backup_corrupt3" ],
 	1,
 	[qr{^$}],
 	[qr/^WARNING.*7 total checksum verification failures/s],
@@ -553,7 +553,7 @@ rmtree("$tempdir/backup_corrupt3");
 # do not verify checksums, should return ok
 $node->command_ok(
 	[
-		'pg_basebackup',            '-D',
+		'pg', 'basebackup',         '-D',
 		"$tempdir/backup_corrupt4", '--no-verify-checksums'
 	],
 	'pg_basebackup with -k does not report checksum mismatch');
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 6e2f051187..57c36103ee 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -19,27 +19,27 @@ my $stream_dir = $primary->basedir . '/archive_wal';
 mkdir($stream_dir);
 
 # Sanity checks for command line options.
-$primary->command_fails(['pg_receivewal'],
+$primary->command_fails(['pg', 'receivewal'],
 	'pg_receivewal needs target directory specified');
 $primary->command_fails(
-	[ 'pg_receivewal', '-D', $stream_dir, '--create-slot', '--drop-slot' ],
+	[ 'pg', 'receivewal', '-D', $stream_dir, '--create-slot', '--drop-slot' ],
 	'failure if both --create-slot and --drop-slot specified');
 $primary->command_fails(
-	[ 'pg_receivewal', '-D', $stream_dir, '--create-slot' ],
+	[ 'pg', 'receivewal', '-D', $stream_dir, '--create-slot' ],
 	'failure if --create-slot specified without --slot');
 $primary->command_fails(
-	[ 'pg_receivewal', '-D', $stream_dir, '--synchronous', '--no-sync' ],
+	[ 'pg', 'receivewal', '-D', $stream_dir, '--synchronous', '--no-sync' ],
 	'failure if --synchronous specified with --no-sync');
 
 # Slot creation and drop
 my $slot_name = 'test';
 $primary->command_ok(
-	[ 'pg_receivewal', '--slot', $slot_name, '--create-slot' ],
+	[ 'pg', 'receivewal', '--slot', $slot_name, '--create-slot' ],
 	'creating a replication slot');
 my $slot = $primary->slot($slot_name);
 is($slot->{'slot_type'}, 'physical', 'physical replication slot was created');
 is($slot->{'restart_lsn'}, '', 'restart LSN of new slot is null');
-$primary->command_ok([ 'pg_receivewal', '--slot', $slot_name, '--drop-slot' ],
+$primary->command_ok([ 'pg', 'receivewal', '--slot', $slot_name, '--drop-slot' ],
 	'dropping a replication slot');
 is($primary->slot($slot_name)->{'slot_type'},
 	'', 'replication slot was removed');
@@ -58,7 +58,7 @@ $primary->psql('postgres',
 # Stream up to the given position.
 $primary->command_ok(
 	[
-		'pg_receivewal', '-D',     $stream_dir,     '--verbose',
+		'pg', 'receivewal', '-D',     $stream_dir,     '--verbose',
 		'--endpos',      $nextlsn, '--synchronous', '--no-loop'
 	],
 	'streaming some WAL with --synchronous');
diff --git a/src/bin/pg_basebackup/t/030_pg_recvlogical.pl b/src/bin/pg_basebackup/t/030_pg_recvlogical.pl
index 99154bcf39..9d84e39c6a 100644
--- a/src/bin/pg_basebackup/t/030_pg_recvlogical.pl
+++ b/src/bin/pg_basebackup/t/030_pg_recvlogical.pl
@@ -23,14 +23,14 @@ log_error_verbosity = verbose
 $node->dump_info;
 $node->start;
 
-$node->command_fails(['pg_recvlogical'], 'pg_recvlogical needs a slot name');
-$node->command_fails([ 'pg_recvlogical', '-S', 'test' ],
+$node->command_fails([ 'pg', 'recvlogical'], 'pg_recvlogical needs a slot name');
+$node->command_fails([ 'pg', 'recvlogical', '-S', 'test' ],
 	'pg_recvlogical needs a database');
-$node->command_fails([ 'pg_recvlogical', '-S', 'test', '-d', 'postgres' ],
+$node->command_fails([ 'pg', 'recvlogical', '-S', 'test', '-d', 'postgres' ],
 	'pg_recvlogical needs an action');
 $node->command_fails(
 	[
-		'pg_recvlogical',           '-S',
+		'pg', 'recvlogical',        '-S',
 		'test',                     '-d',
 		$node->connstr('postgres'), '--start'
 	],
diff --git a/src/bin/pg_checksums/Makefile b/src/bin/pg_checksums/Makefile
index b1cfa5733d..991704bb52 100644
--- a/src/bin/pg_checksums/Makefile
+++ b/src/bin/pg_checksums/Makefile
@@ -25,13 +25,13 @@ pg_checksums: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_checksums$(X) '$(DESTDIR)$(bindir)/pg_checksums$(X)'
+	$(INSTALL_PROGRAM) pg_checksums$(X) '$(DESTDIR)$(libexecdir)/pg_checksums$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_checksums$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_checksums$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_checksums$(X) $(OBJS)
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 4e4934532a..fb91a1ecdb 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -41,7 +41,7 @@ sub check_relation_corruption
 	# corrupted yet.
 	command_ok(
 		[
-			'pg_checksums', '--check',
+			'pg', 'checksums', '--check',
 			'-D',           $pgdata,
 			'--filenode',   $relfilenode_corrupted
 		],
@@ -57,7 +57,7 @@ sub check_relation_corruption
 	# Checksum checks on single relfilenode fail
 	$node->command_checks_all(
 		[
-			'pg_checksums', '--check',
+			'pg', 'checksums', '--check',
 			'-D',           $pgdata,
 			'--filenode',   $relfilenode_corrupted
 		],
@@ -69,7 +69,7 @@ sub check_relation_corruption
 
 	# Global checksum checks fail as well
 	$node->command_checks_all(
-		[ 'pg_checksums', '--check', '-D', $pgdata ],
+		[ 'pg', 'checksums', '--check', '-D', $pgdata ],
 		1,
 		[qr/Bad checksums:.*1/],
 		[qr/checksum verification failed/],
@@ -79,7 +79,7 @@ sub check_relation_corruption
 	$node->start;
 	$node->safe_psql('postgres', "DROP TABLE $table;");
 	$node->stop;
-	$node->command_ok([ 'pg_checksums', '--check', '-D', $pgdata ],
+	$node->command_ok([ 'pg', 'checksums', '--check', '-D', $pgdata ],
 		"succeeds again after table drop on tablespace $tablespace");
 
 	$node->start;
@@ -116,66 +116,66 @@ append_to_file "$pgdata/global/pg_internal.init",     "foo";
 append_to_file "$pgdata/global/pg_internal.init.123", "foo";
 
 # Enable checksums.
-command_ok([ 'pg_checksums', '--enable', '--no-sync', '-D', $pgdata ],
+command_ok([ 'pg', 'checksums', '--enable', '--no-sync', '-D', $pgdata ],
 	"checksums successfully enabled in cluster");
 
 # Successive attempt to enable checksums fails.
-command_fails([ 'pg_checksums', '--enable', '--no-sync', '-D', $pgdata ],
+command_fails([ 'pg', 'checksums', '--enable', '--no-sync', '-D', $pgdata ],
 	"enabling checksums fails if already enabled");
 
 # Control file should know that checksums are enabled.
 command_like(
-	[ 'pg_controldata', $pgdata ],
+	[ 'pg', 'controldata', $pgdata ],
 	qr/Data page checksum version:.*1/,
 	'checksums enabled in control file');
 
 # Disable checksums again.  Flush result here as that should be cheap.
 command_ok(
-	[ 'pg_checksums', '--disable', '-D', $pgdata ],
+	[ 'pg', 'checksums', '--disable', '-D', $pgdata ],
 	"checksums successfully disabled in cluster");
 
 # Successive attempt to disable checksums fails.
 command_fails(
-	[ 'pg_checksums', '--disable', '--no-sync', '-D', $pgdata ],
+	[ 'pg', 'checksums', '--disable', '--no-sync', '-D', $pgdata ],
 	"disabling checksums fails if already disabled");
 
 # Control file should know that checksums are disabled.
 command_like(
-	[ 'pg_controldata', $pgdata ],
+	[ 'pg', 'controldata', $pgdata ],
 	qr/Data page checksum version:.*0/,
 	'checksums disabled in control file');
 
 # Enable checksums again for follow-up tests.
-command_ok([ 'pg_checksums', '--enable', '--no-sync', '-D', $pgdata ],
+command_ok([ 'pg', 'checksums', '--enable', '--no-sync', '-D', $pgdata ],
 	"checksums successfully enabled in cluster");
 
 # Control file should know that checksums are enabled.
 command_like(
-	[ 'pg_controldata', $pgdata ],
+	[ 'pg', 'controldata', $pgdata ],
 	qr/Data page checksum version:.*1/,
 	'checksums enabled in control file');
 
 # Checksums pass on a newly-created cluster
-command_ok([ 'pg_checksums', '--check', '-D', $pgdata ],
+command_ok([ 'pg', 'checksums', '--check', '-D', $pgdata ],
 	"succeeds with offline cluster");
 
 # Checksums are verified if no other arguments are specified
 command_ok(
-	[ 'pg_checksums', '-D', $pgdata ],
+	[ 'pg', 'checksums', '-D', $pgdata ],
 	"verifies checksums as default action");
 
 # Specific relation files cannot be requested when action is --disable
 # or --enable.
 command_fails(
-	[ 'pg_checksums', '--disable', '--filenode', '1234', '-D', $pgdata ],
+	[ 'pg', 'checksums', '--disable', '--filenode', '1234', '-D', $pgdata ],
 	"fails when relfilenodes are requested and action is --disable");
 command_fails(
-	[ 'pg_checksums', '--enable', '--filenode', '1234', '-D', $pgdata ],
+	[ 'pg', 'checksums', '--enable', '--filenode', '1234', '-D', $pgdata ],
 	"fails when relfilenodes are requested and action is --enable");
 
 # Checks cannot happen with an online cluster
 $node->start;
-command_fails([ 'pg_checksums', '--check', '-D', $pgdata ],
+command_fails([ 'pg', 'checksums', '--check', '-D', $pgdata ],
 	"fails with online cluster");
 
 # Check corruption of table on default tablespace.
@@ -203,7 +203,7 @@ sub fail_corrupt
 	append_to_file $file_name, "foo";
 
 	$node->command_checks_all(
-		[ 'pg_checksums', '--check', '-D', $pgdata ],
+		[ 'pg', 'checksums', '--check', '-D', $pgdata ],
 		1,
 		[qr/^$/],
 		[qr/could not read block 0 in file.*$file\":/],
@@ -221,7 +221,7 @@ $node->stop;
 # when verifying checksums.
 mkdir "$tablespace_dir/PG_99_999999991/";
 append_to_file "$tablespace_dir/PG_99_999999991/foo", "123";
-command_ok([ 'pg_checksums', '--check', '-D', $pgdata ],
+command_ok([ 'pg', 'checksums', '--check', '-D', $pgdata ],
 	"succeeds with foreign tablespace");
 
 # Authorized relation files filled with corrupted data cause the
diff --git a/src/bin/pg_checksums/t/003_redirect.pl b/src/bin/pg_checksums/t/003_redirect.pl
new file mode 100644
index 0000000000..64aff19225
--- /dev/null
+++ b/src/bin/pg_checksums/t/003_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'checksums'],
+	qr/error: no data directory specified/,
+	'pg pg_checksums fails');
+
+command_ok(
+	['pg', 'checksums', '--version'],
+	'pg pg_checksums version ok');
+
+command_ok(
+	['pg', 'checksums', '--help'],
+	'pg pg_checksums help ok');
diff --git a/src/bin/pg_config/Makefile b/src/bin/pg_config/Makefile
index d3b5f1fa75..c8c8dd943a 100644
--- a/src/bin/pg_config/Makefile
+++ b/src/bin/pg_config/Makefile
@@ -25,13 +25,13 @@ pg_config: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_SCRIPT) pg_config$(X) '$(DESTDIR)$(bindir)/pg_config$(X)'
+	$(INSTALL_SCRIPT) pg_config$(X) '$(DESTDIR)$(libexecdir)/pg_config$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_config$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_config$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_config$(X) $(OBJS)
diff --git a/src/bin/pg_config/pg_config.c b/src/bin/pg_config/pg_config.c
index f5410f6418..61676ff0d2 100644
--- a/src/bin/pg_config/pg_config.c
+++ b/src/bin/pg_config/pg_config.c
@@ -42,6 +42,7 @@ typedef struct
 
 static const InfoItem info_items[] = {
 	{"--bindir", "BINDIR"},
+	{"--libexecdir", "LIBEXECDIR"},
 	{"--docdir", "DOCDIR"},
 	{"--htmldir", "HTMLDIR"},
 	{"--includedir", "INCLUDEDIR"},
@@ -76,6 +77,7 @@ help(void)
 	printf(_("  %s [OPTION]...\n\n"), progname);
 	printf(_("Options:\n"));
 	printf(_("  --bindir              show location of user executables\n"));
+	printf(_("  --libexecdir          show location of commands\n"));
 	printf(_("  --docdir              show location of documentation files\n"));
 	printf(_("  --htmldir             show location of HTML documentation files\n"));
 	printf(_("  --includedir          show location of C header files of the client\n"
@@ -132,6 +134,7 @@ main(int argc, char **argv)
 	ConfigData *configdata;
 	size_t		configdata_len;
 	char		my_exec_path[MAXPGPATH];
+	char		my_rootdir[MAXPGPATH];
 	int			i;
 	int			j;
 
@@ -147,6 +150,11 @@ main(int argc, char **argv)
 			help();
 			exit(0);
 		}
+		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+		{
+			puts("pg_config (PostgreSQL) " PG_VERSION);
+			exit(0);
+		}
 	}
 
 	if (find_my_exec(argv[0], my_exec_path) < 0)
@@ -155,7 +163,13 @@ main(int argc, char **argv)
 		exit(1);
 	}
 
-	configdata = get_configdata(my_exec_path, &configdata_len);
+	if (find_my_rootdir(argv[0], my_rootdir) < 0)
+	{
+		fprintf(stderr, _("%s: could not find own root executable\n"), progname);
+		exit(1);
+	}
+
+	configdata = get_configdata(my_exec_path, my_rootdir, &configdata_len);
 	/* no arguments -> print everything */
 	if (argc < 2)
 	{
diff --git a/src/bin/pg_config/t/001_pg_config.pl b/src/bin/pg_config/t/001_pg_config.pl
index ccca190bb1..07d85342bb 100644
--- a/src/bin/pg_config/t/001_pg_config.pl
+++ b/src/bin/pg_config/t/001_pg_config.pl
@@ -1,16 +1,18 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 23;
 
 program_help_ok('pg_config');
 program_version_ok('pg_config');
 program_options_handling_ok('pg_config');
 command_like([ 'pg_config', '--bindir' ], qr/bin/, 'pg_config single option')
   ;    # XXX might be wrong
-command_like([ 'pg_config', '--bindir', '--libdir' ],
-	qr/bin.*\n.*lib/, 'pg_config two options');
-command_like([ 'pg_config', '--libdir', '--bindir' ],
-	qr/lib.*\n.*bin/, 'pg_config two options different order');
+command_like([ 'pg_config', '--bindir', '--libdir', '--libexecdir' ],
+	qr/bin.*\n.*lib.*\n.*libexec/, 'pg_config three options');
+command_like([ 'pg_config', '--libexecdir', '--libdir', '--bindir' ],
+	qr/libexec.*\n.*lib.*\n.*bin/, 'pg_config three options different order');
+command_like(['pg_config'],
+	qr/libexec/, 'pg_config without options includes libexec in the output');
 command_like(['pg_config'], qr/.*\n.*\n.*/,
 	'pg_config without options prints many lines');
diff --git a/src/bin/pg_controldata/Makefile b/src/bin/pg_controldata/Makefile
index 76b330dc1f..dc22ad9217 100644
--- a/src/bin/pg_controldata/Makefile
+++ b/src/bin/pg_controldata/Makefile
@@ -25,13 +25,13 @@ pg_controldata: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_controldata$(X) '$(DESTDIR)$(bindir)/pg_controldata$(X)'
+	$(INSTALL_PROGRAM) pg_controldata$(X) '$(DESTDIR)$(libexecdir)/pg_controldata$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_controldata$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_controldata$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_controldata$(X) $(OBJS)
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..5b6f8d4938 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -7,14 +7,14 @@ use Test::More tests => 17;
 program_help_ok('pg_controldata');
 program_version_ok('pg_controldata');
 program_options_handling_ok('pg_controldata');
-command_fails(['pg_controldata'], 'pg_controldata without arguments fails');
-command_fails([ 'pg_controldata', 'nonexistent' ],
+command_fails([ 'pg', 'controldata'], 'pg_controldata without arguments fails');
+command_fails([ 'pg', 'controldata', 'nonexistent' ],
 	'pg_controldata with nonexistent directory fails');
 
 my $node = get_new_node('main');
 $node->init;
 
-command_like([ 'pg_controldata', $node->data_dir ],
+command_like([ 'pg', 'controldata', $node->data_dir ],
 	qr/checkpoint/, 'pg_controldata produces output');
 
 
@@ -31,7 +31,7 @@ print $fh pack("x[$size]");
 close $fh;
 
 command_checks_all(
-	[ 'pg_controldata', $node->data_dir ],
+	[ 'pg', 'controldata', $node->data_dir ],
 	0,
 	[
 		qr/WARNING: Calculated CRC checksum does not match value stored in file/,
diff --git a/src/bin/pg_controldata/t/002_redirect.pl b/src/bin/pg_controldata/t/002_redirect.pl
new file mode 100644
index 0000000000..171f7523fa
--- /dev/null
+++ b/src/bin/pg_controldata/t/002_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'controldata'],
+	qr/error: no data directory specified/,
+	'pg controldata fails');
+
+command_ok(
+	['pg', 'controldata', '--version'],
+	'pg controldata version ok');
+
+command_ok(
+	['pg', 'controldata', '--help'],
+	'pg controldata help ok');
diff --git a/src/bin/pg_ctl/Makefile b/src/bin/pg_ctl/Makefile
index 14602c1185..08b6ccaf76 100644
--- a/src/bin/pg_ctl/Makefile
+++ b/src/bin/pg_ctl/Makefile
@@ -34,13 +34,13 @@ pg_ctl: $(OBJS) | submake-libpgport $(SUBMAKE_LIBPQ)
 	$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_ctl$(X) '$(DESTDIR)$(bindir)/pg_ctl$(X)'
+	$(INSTALL_PROGRAM) pg_ctl$(X) '$(DESTDIR)$(libexecdir)/pg_ctl$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_ctl$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_ctl$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_ctl$(X) $(OBJS)
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 3c03ace7ed..018dfe094b 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -795,14 +795,14 @@ trap_sigint_during_startup(int sig)
 }
 
 static char *
-find_other_exec_or_die(const char *argv0, const char *target, const char *versionstr)
+find_other_cmd_or_die(const char *argv0, const char *target, const char *versionstr)
 {
 	int			ret;
 	char	   *found_path;
 
 	found_path = pg_malloc(MAXPGPATH);
 
-	if ((ret = find_other_exec(argv0, target, versionstr, found_path)) < 0)
+	if ((ret = find_other_cmd(argv0, target, versionstr, found_path)) < 0)
 	{
 		char		full_path[MAXPGPATH];
 
@@ -831,7 +831,7 @@ do_init(void)
 	char		cmd[MAXPGPATH];
 
 	if (exec_path == NULL)
-		exec_path = find_other_exec_or_die(argv0, "initdb", "initdb (PostgreSQL) " PG_VERSION "\n");
+		exec_path = find_other_cmd_or_die(argv0, "initdb", "initdb (PostgreSQL) " PG_VERSION "\n");
 
 	if (pgdata_opt == NULL)
 		pgdata_opt = "";
@@ -875,7 +875,7 @@ do_start(void)
 		pgdata_opt = "";
 
 	if (exec_path == NULL)
-		exec_path = find_other_exec_or_die(argv0, "postgres", PG_BACKEND_VERSIONSTR);
+		exec_path = find_other_cmd_or_die(argv0, "postgres", PG_BACKEND_VERSIONSTR);
 
 #if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
 	if (allow_core_files)
@@ -1443,7 +1443,7 @@ pgwin32_CommandLine(bool registration)
 	}
 	else
 	{
-		ret = find_other_exec(argv0, "postgres", PG_BACKEND_VERSIONSTR,
+		ret = find_other_cmd(argv0, "postgres", PG_BACKEND_VERSIONSTR,
 							  cmdPath);
 		if (ret != 0)
 		{
@@ -2208,7 +2208,7 @@ adjust_data_dir(void)
 
 	/* we use a private my_exec_path to avoid interfering with later uses */
 	if (exec_path == NULL)
-		my_exec_path = find_other_exec_or_die(argv0, "postgres", PG_BACKEND_VERSIONSTR);
+		my_exec_path = find_other_cmd_or_die(argv0, "postgres", PG_BACKEND_VERSIONSTR);
 	else
 		my_exec_path = pg_strdup(exec_path);
 
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
index b1e419f02e..964e8f046e 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -15,10 +15,10 @@ program_help_ok('pg_ctl');
 program_version_ok('pg_ctl');
 program_options_handling_ok('pg_ctl');
 
-command_exit_is([ 'pg_ctl', 'start', '-D', "$tempdir/nonexistent" ],
+command_exit_is([ 'pg', 'ctl', 'start', '-D', "$tempdir/nonexistent" ],
 	1, 'pg_ctl start with nonexistent directory');
 
-command_ok([ 'pg_ctl', 'initdb', '-D', "$tempdir/data", '-o', '-N' ],
+command_ok([ 'pg', 'ctl', 'initdb', '-D', "$tempdir/data", '-o', '-N' ],
 	'pg_ctl initdb');
 command_ok([ $ENV{PG_REGRESS}, '--config-auth', "$tempdir/data" ],
 	'configure authentication');
@@ -40,7 +40,7 @@ else
 }
 close $conf;
 my $ctlcmd = [
-	'pg_ctl', 'start', '-D', "$tempdir/data", '-l',
+	'pg', 'ctl', 'start', '-D', "$tempdir/data", '-l',
 	"$TestLib::log_path/001_start_stop_server.log"
 ];
 if ($Config{osname} ne 'msys')
@@ -59,17 +59,17 @@ else
 # postmaster they start.  Waiting more than the 2 seconds slop time allowed
 # by wait_for_postmaster() prevents that mistake.
 sleep 3 if ($windows_os);
-command_fails([ 'pg_ctl', 'start', '-D', "$tempdir/data" ],
+command_fails([ 'pg', 'ctl', 'start', '-D', "$tempdir/data" ],
 	'second pg_ctl start fails');
-command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
-command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
+command_ok([ 'pg', 'ctl', 'stop', '-D', "$tempdir/data" ], 'pg_ctl stop');
+command_fails([ 'pg', 'ctl', 'stop', '-D', "$tempdir/data" ],
 	'second pg_ctl stop fails');
 
 # Log file for default permission test.  The permissions won't be checked on
 # Windows but we still want to do the restart test.
 my $logFileName = "$tempdir/data/perm-test-600.log";
 
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+command_ok([ 'pg', 'ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
 	'pg_ctl restart with server not running');
 
 # Permissions on log file should be default
@@ -89,21 +89,21 @@ SKIP:
 {
 	skip "group access not supported on Windows", 3 if ($windows_os);
 
-	system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+	system_or_bail 'pg', 'ctl', 'stop', '-D', "$tempdir/data";
 
 	# Change the data dir mode so log file will be created with group read
 	# privileges on the next start
 	chmod_recursive("$tempdir/data", 0750, 0640);
 
 	command_ok(
-		[ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+		[ 'pg', 'ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
 		'start server to check group permissions');
 
 	ok(-f $logFileName);
 	ok(check_mode_recursive("$tempdir/data", 0750, 0640));
 }
 
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+command_ok([ 'pg', 'ctl', 'restart', '-D', "$tempdir/data" ],
 	'pg_ctl restart with server running');
 
-system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+system_or_bail 'pg', 'ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_ctl/t/002_status.pl b/src/bin/pg_ctl/t/002_status.pl
index 606d10560f..4f8f72e8bc 100644
--- a/src/bin/pg_ctl/t/002_status.pl
+++ b/src/bin/pg_ctl/t/002_status.pl
@@ -8,18 +8,18 @@ use Test::More tests => 3;
 my $tempdir       = TestLib::tempdir;
 my $tempdir_short = TestLib::tempdir_short;
 
-command_exit_is([ 'pg_ctl', 'status', '-D', "$tempdir/nonexistent" ],
+command_exit_is([ 'pg', 'ctl', 'status', '-D', "$tempdir/nonexistent" ],
 	4, 'pg_ctl status with nonexistent directory');
 
 my $node = get_new_node('main');
 $node->init;
 
-command_exit_is([ 'pg_ctl', 'status', '-D', $node->data_dir ],
+command_exit_is([ 'pg', 'ctl', 'status', '-D', $node->data_dir ],
 	3, 'pg_ctl status with server not running');
 
-system_or_bail 'pg_ctl', '-l', "$tempdir/logfile", '-D',
+system_or_bail 'pg', 'ctl', '-l', "$tempdir/logfile", '-D',
   $node->data_dir, '-w', 'start';
-command_exit_is([ 'pg_ctl', 'status', '-D', $node->data_dir ],
+command_exit_is([ 'pg', 'ctl', 'status', '-D', $node->data_dir ],
 	0, 'pg_ctl status with server running');
 
-system_or_bail 'pg_ctl', 'stop', '-D', $node->data_dir;
+system_or_bail 'pg', 'ctl', 'stop', '-D', $node->data_dir;
diff --git a/src/bin/pg_ctl/t/003_promote.pl b/src/bin/pg_ctl/t/003_promote.pl
index ecb294b490..d371c9b4a3 100644
--- a/src/bin/pg_ctl/t/003_promote.pl
+++ b/src/bin/pg_ctl/t/003_promote.pl
@@ -8,7 +8,7 @@ use Test::More tests => 12;
 my $tempdir = TestLib::tempdir;
 
 command_fails_like(
-	[ 'pg_ctl', '-D', "$tempdir/nonexistent", 'promote' ],
+	[ 'pg', 'ctl', '-D', "$tempdir/nonexistent", 'promote' ],
 	qr/directory .* does not exist/,
 	'pg_ctl promote with nonexistent directory');
 
@@ -16,14 +16,14 @@ my $node_primary = get_new_node('primary');
 $node_primary->init(allows_streaming => 1);
 
 command_fails_like(
-	[ 'pg_ctl', '-D', $node_primary->data_dir, 'promote' ],
+	[ 'pg', 'ctl', '-D', $node_primary->data_dir, 'promote' ],
 	qr/PID file .* does not exist/,
 	'pg_ctl promote of not running instance fails');
 
 $node_primary->start;
 
 command_fails_like(
-	[ 'pg_ctl', '-D', $node_primary->data_dir, 'promote' ],
+	[ 'pg', 'ctl', '-D', $node_primary->data_dir, 'promote' ],
 	qr/not in standby mode/,
 	'pg_ctl promote of primary instance fails');
 
@@ -36,7 +36,7 @@ $node_standby->start;
 is($node_standby->safe_psql('postgres', 'SELECT pg_is_in_recovery()'),
 	't', 'standby is in recovery');
 
-command_ok([ 'pg_ctl', '-D', $node_standby->data_dir, '-W', 'promote' ],
+command_ok([ 'pg', 'ctl', '-D', $node_standby->data_dir, '-W', 'promote' ],
 	'pg_ctl -W promote of standby runs');
 
 ok( $node_standby->poll_query_until(
@@ -52,7 +52,7 @@ $node_standby->start;
 is($node_standby->safe_psql('postgres', 'SELECT pg_is_in_recovery()'),
 	't', 'standby is in recovery');
 
-command_ok([ 'pg_ctl', '-D', $node_standby->data_dir, 'promote' ],
+command_ok([ 'pg', 'ctl', '-D', $node_standby->data_dir, 'promote' ],
 	'pg_ctl promote of standby runs');
 
 # no wait here
diff --git a/src/bin/pg_ctl/t/005_redirect.pl b/src/bin/pg_ctl/t/005_redirect.pl
new file mode 100644
index 0000000000..ae92a81d74
--- /dev/null
+++ b/src/bin/pg_ctl/t/005_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'ctl'],
+	qr/pg_ctl: no operation specified/,
+	'pg pg_ctl fails');
+
+command_ok(
+	['pg', 'ctl', '--version'],
+	'pg pg_ctl version ok');
+
+command_ok(
+	['pg', 'ctl', '--help'],
+	'pg pg_ctl help ok');
diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 2532d9183a..76f23155fe 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -44,12 +44,12 @@ pg_dumpall: pg_dumpall.o dumputils.o | submake-libpq submake-libpgport submake-l
 	$(CC) $(CFLAGS) pg_dumpall.o dumputils.o $(WIN32RES) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_dump$(X) '$(DESTDIR)$(bindir)'/pg_dump$(X)
-	$(INSTALL_PROGRAM) pg_restore$(X) '$(DESTDIR)$(bindir)'/pg_restore$(X)
-	$(INSTALL_PROGRAM) pg_dumpall$(X) '$(DESTDIR)$(bindir)'/pg_dumpall$(X)
+	$(INSTALL_PROGRAM) pg_dump$(X) '$(DESTDIR)$(libexecdir)'/pg_dump$(X)
+	$(INSTALL_PROGRAM) pg_restore$(X) '$(DESTDIR)$(libexecdir)'/pg_restore$(X)
+	$(INSTALL_PROGRAM) pg_dumpall$(X) '$(DESTDIR)$(libexecdir)'/pg_dumpall$(X)
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 check:
 	$(prove_check)
@@ -58,7 +58,7 @@ installcheck:
 	$(prove_installcheck)
 
 uninstall:
-	rm -f $(addprefix '$(DESTDIR)$(bindir)'/, pg_dump$(X) pg_restore$(X) pg_dumpall$(X))
+	rm -f $(addprefix '$(DESTDIR)$(libexecdir)'/, pg_dump$(X) pg_restore$(X) pg_dumpall$(X))
 
 clean distclean maintainer-clean:
 	rm -f pg_dump$(X) pg_restore$(X) pg_dumpall$(X) $(OBJS) pg_dump.o common.o pg_dump_sort.o pg_restore.o pg_dumpall.o
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 8d54849102..fdfe571d4e 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -187,8 +187,8 @@ main(int argc, char *argv[])
 		}
 	}
 
-	if ((ret = find_other_exec(argv[0], "pg_dump", PGDUMP_VERSIONSTR,
-							   pg_dump_bin)) < 0)
+	if ((ret = find_other_cmd(argv[0], "pg_dump", PGDUMP_VERSIONSTR,
+							  pg_dump_bin)) < 0)
 	{
 		char		full_path[MAXPGPATH];
 
diff --git a/src/bin/pg_dump/t/004_redirect.pl b/src/bin/pg_dump/t/004_redirect.pl
new file mode 100644
index 0000000000..f2498cf534
--- /dev/null
+++ b/src/bin/pg_dump/t/004_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'dump'],
+	qr/error: connection to database .* failed/,
+	'pg pg_dump fails');
+
+command_ok(
+	['pg', 'dump', '--version'],
+	'pg pg_dump version ok');
+
+command_ok(
+	['pg', 'dump', '--help'],
+	'pg pg_dump help ok');
diff --git a/src/bin/pg_resetwal/Makefile b/src/bin/pg_resetwal/Makefile
index 464268e978..00fafc6fea 100644
--- a/src/bin/pg_resetwal/Makefile
+++ b/src/bin/pg_resetwal/Makefile
@@ -25,13 +25,13 @@ pg_resetwal: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_resetwal$(X) '$(DESTDIR)$(bindir)/pg_resetwal$(X)'
+	$(INSTALL_PROGRAM) pg_resetwal$(X) '$(DESTDIR)$(libexecdir)/pg_resetwal$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_resetwal$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_resetwal$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_resetwal$(X) $(OBJS)
diff --git a/src/bin/pg_resetwal/t/003_redirect.pl b/src/bin/pg_resetwal/t/003_redirect.pl
new file mode 100644
index 0000000000..f70467110f
--- /dev/null
+++ b/src/bin/pg_resetwal/t/003_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'resetwal'],
+	qr/error: no data directory specified/,
+	'pg pg_resetwal fails');
+
+command_ok(
+	['pg', 'resetwal', '--version'],
+	'pg pg_resetwal version ok');
+
+command_ok(
+	['pg', 'resetwal', '--help'],
+	'pg pg_resetwal help ok');
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index f398c3d848..dd59c284eb 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -42,13 +42,13 @@ xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
 	rm -f $@ && $(LN_S) $< .
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_rewind$(X) '$(DESTDIR)$(bindir)/pg_rewind$(X)'
+	$(INSTALL_PROGRAM) pg_rewind$(X) '$(DESTDIR)$(libexecdir)/pg_rewind$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_rewind$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_rewind$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_rewind$(X) $(OBJS) xlogreader.c
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 0015d3b461..a8d7b60d10 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -835,9 +835,9 @@ getRestoreCommand(const char *argv0)
 		return;
 
 	/* find postgres executable */
-	rc = find_other_exec(argv0, "postgres",
-						 PG_BACKEND_VERSIONSTR,
-						 postgres_exec_path);
+	rc = find_other_cmd(argv0, "postgres",
+						PG_BACKEND_VERSIONSTR,
+						postgres_exec_path);
 
 	if (rc < 0)
 	{
@@ -895,9 +895,9 @@ ensureCleanShutdown(const char *argv0)
 	char		cmd[MAXCMDLEN];
 
 	/* locate postgres binary */
-	if ((ret = find_other_exec(argv0, "postgres",
-							   PG_BACKEND_VERSIONSTR,
-							   exec_path)) < 0)
+	if ((ret = find_other_cmd(argv0, "postgres",
+							  PG_BACKEND_VERSIONSTR,
+							  exec_path)) < 0)
 	{
 		char		full_path[MAXPGPATH];
 
diff --git a/src/bin/pg_rewind/t/007_redirect.pl b/src/bin/pg_rewind/t/007_redirect.pl
new file mode 100644
index 0000000000..9d4be4d081
--- /dev/null
+++ b/src/bin/pg_rewind/t/007_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'rewind'],
+	qr/error: no source specified/,
+	'pg pg_rewind fails');
+
+command_ok(
+	['pg', 'rewind', '--version'],
+	'pg pg_rewind version ok');
+
+command_ok(
+	['pg', 'rewind', '--help'],
+	'pg pg_rewind help ok');
diff --git a/src/bin/pg_test_fsync/Makefile b/src/bin/pg_test_fsync/Makefile
index 7632c94eb7..7ef63a172b 100644
--- a/src/bin/pg_test_fsync/Makefile
+++ b/src/bin/pg_test_fsync/Makefile
@@ -17,13 +17,13 @@ pg_test_fsync: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_test_fsync$(X) '$(DESTDIR)$(bindir)/pg_test_fsync$(X)'
+	$(INSTALL_PROGRAM) pg_test_fsync$(X) '$(DESTDIR)$(libexecdir)/pg_test_fsync$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_test_fsync$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_test_fsync$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_test_fsync$(X) $(OBJS)
diff --git a/src/bin/pg_test_timing/Makefile b/src/bin/pg_test_timing/Makefile
index 334d6ff5c0..bc762113ab 100644
--- a/src/bin/pg_test_timing/Makefile
+++ b/src/bin/pg_test_timing/Makefile
@@ -17,13 +17,13 @@ pg_test_timing: $(OBJS) | submake-libpgport
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_test_timing$(X) '$(DESTDIR)$(bindir)/pg_test_timing$(X)'
+	$(INSTALL_PROGRAM) pg_test_timing$(X) '$(DESTDIR)$(libexecdir)/pg_test_timing$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_test_timing$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_test_timing$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_test_timing$(X) $(OBJS)
diff --git a/src/bin/pg_upgrade/Makefile b/src/bin/pg_upgrade/Makefile
index 0360c37bf9..da9bf0e589 100644
--- a/src/bin/pg_upgrade/Makefile
+++ b/src/bin/pg_upgrade/Makefile
@@ -34,13 +34,13 @@ pg_upgrade: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_upgrade$(X) '$(DESTDIR)$(bindir)/pg_upgrade$(X)'
+	$(INSTALL_PROGRAM) pg_upgrade$(X) '$(DESTDIR)$(libexecdir)/pg_upgrade$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_upgrade$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_upgrade$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_upgrade$(X) $(OBJS)
@@ -59,7 +59,7 @@ clean distclean maintainer-clean:
 NOTSUBMAKEMAKE=$(MAKE)
 
 check: test.sh all temp-install
-	MAKE=$(NOTSUBMAKEMAKE) $(with_temp_install) bindir=$(abs_top_builddir)/tmp_install/$(bindir) EXTRA_REGRESS_OPTS="$(EXTRA_REGRESS_OPTS)" $(SHELL) $<
+	MAKE=$(NOTSUBMAKEMAKE) $(with_temp_install) bindir=$(abs_top_builddir)/tmp_install/$(bindir) libexecdir=$(abs_top_builddir)/tmp_install/$(libexecdir) EXTRA_REGRESS_OPTS="$(EXTRA_REGRESS_OPTS)" $(SHELL) $<
 
 # installcheck is not supported because there's no meaningful way to test
 # pg_upgrade against a single already-running server
diff --git a/src/bin/pg_upgrade/TESTING b/src/bin/pg_upgrade/TESTING
index e69874b42d..6e01e891d9 100644
--- a/src/bin/pg_upgrade/TESTING
+++ b/src/bin/pg_upgrade/TESTING
@@ -14,7 +14,9 @@ must have done "make install" for both versions.  Then do:
 
 export oldsrc=...somewhere/postgresql	(old version's source tree)
 export oldbindir=...otherversion/bin	(old version's installed bin dir)
+export oldlibexecdir=...otherversion/libexec	(old version's installed libexec dir)
 export bindir=...thisversion/bin	(this version's installed bin dir)
+export libexecdir=...thisversion/libexec	(this version's installed libexec dir)
 export libdir=...thisversion/lib	(this version's installed lib dir)
 sh test.sh
 
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef855dc..e025a89dd6 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -503,17 +503,17 @@ create_script_for_cluster_analyze(char **analyze_script_file_name)
 	fprintf(script, "echo %sthis script and run:%s\n",
 			ECHO_QUOTE, ECHO_QUOTE);
 	fprintf(script, "echo %s    \"%s/vacuumdb\" %s--all %s%s\n", ECHO_QUOTE,
-			new_cluster.bindir, user_specification.data,
+			new_cluster.libexecdir, user_specification.data,
 	/* Did we copy the free space files? */
 			(GET_MAJOR_VERSION(old_cluster.major_version) >= 804) ?
 			"--analyze-only" : "--analyze", ECHO_QUOTE);
 	fprintf(script, "echo%s\n\n", ECHO_BLANK);
 
 	fprintf(script, "\"%s/vacuumdb\" %s--all --analyze-in-stages\n",
-			new_cluster.bindir, user_specification.data);
+			new_cluster.libexecdir, user_specification.data);
 	/* Did we copy the free space files? */
 	if (GET_MAJOR_VERSION(old_cluster.major_version) < 804)
-		fprintf(script, "\"%s/vacuumdb\" %s--all\n", new_cluster.bindir,
+		fprintf(script, "\"%s/vacuumdb\" %s--all\n", new_cluster.libexecdir,
 				user_specification.data);
 
 	fprintf(script, "echo%s\n\n", ECHO_BLANK);
diff --git a/src/bin/pg_upgrade/controldata.c b/src/bin/pg_upgrade/controldata.c
index 00d71e3a8a..24587c6b8e 100644
--- a/src/bin/pg_upgrade/controldata.c
+++ b/src/bin/pg_upgrade/controldata.c
@@ -119,7 +119,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
 	{
 		/* only pg_controldata outputs the cluster state */
 		snprintf(cmd, sizeof(cmd), "\"%s/pg_controldata\" \"%s\"",
-				 cluster->bindir, cluster->pgdata);
+				 cluster->libexecdir, cluster->pgdata);
 		fflush(stdout);
 		fflush(stderr);
 
@@ -185,7 +185,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
 	else
 		resetwal_bin = "pg_resetwal\" -n";
 	snprintf(cmd, sizeof(cmd), "\"%s/%s \"%s\"",
-			 cluster->bindir,
+			 cluster->libexecdir,
 			 live_check ? "pg_controldata\"" : resetwal_bin,
 			 cluster->pgdata);
 	fflush(stdout);
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 4d730adfe2..22c0b64bc4 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -21,9 +21,9 @@ generate_old_dump(void)
 
 	/* run new pg_dumpall binary for globals */
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/pg_dumpall\" %s --globals-only --quote-all-identifiers "
+			  "\"%s\" %s --globals-only --quote-all-identifiers "
 			  "--binary-upgrade %s -f %s",
-			  new_cluster.bindir, cluster_conn_opts(&old_cluster),
+			  new_cluster.pg_dumpall_path, cluster_conn_opts(&old_cluster),
 			  log_opts.verbose ? "--verbose" : "",
 			  GLOBALS_DUMP_FILE);
 	check_ok();
@@ -51,9 +51,9 @@ generate_old_dump(void)
 		snprintf(log_file_name, sizeof(log_file_name), DB_DUMP_LOG_FILE_MASK, old_db->db_oid);
 
 		parallel_exec_prog(log_file_name, NULL,
-						   "\"%s/pg_dump\" %s --schema-only --quote-all-identifiers "
+						   "\"%s\" %s --schema-only --quote-all-identifiers "
 						   "--binary-upgrade --format=custom %s --file=\"%s\" %s",
-						   new_cluster.bindir, cluster_conn_opts(&old_cluster),
+						   new_cluster.pg_dump_path, cluster_conn_opts(&old_cluster),
 						   log_opts.verbose ? "--verbose" : "",
 						   sql_file_name, escaped_connstr.data);
 
diff --git a/src/bin/pg_upgrade/exec.c b/src/bin/pg_upgrade/exec.c
index b31cda8fec..aa4e3fda43 100644
--- a/src/bin/pg_upgrade/exec.c
+++ b/src/bin/pg_upgrade/exec.c
@@ -16,7 +16,7 @@
 static void check_data_dir(ClusterInfo *cluster);
 static void check_bin_dir(ClusterInfo *cluster);
 static void get_bin_version(ClusterInfo *cluster);
-static void validate_exec(const char *dir, const char *cmdName);
+static void validate_exec(ClusterInfo *cluster, char **path, const char *cmdName);
 
 #ifdef WIN32
 static int	win32_check_directory_write_permissions(void);
@@ -37,7 +37,7 @@ get_bin_version(ClusterInfo *cluster)
 	int			v1 = 0,
 				v2 = 0;
 
-	snprintf(cmd, sizeof(cmd), "\"%s/pg_ctl\" --version", cluster->bindir);
+	snprintf(cmd, sizeof(cmd), "\"%s/pg_ctl\" --version", cluster->libexecdir);
 
 	if ((output = popen(cmd, "r")) == NULL ||
 		fgets(cmd_output, sizeof(cmd_output), output) == NULL)
@@ -357,10 +357,9 @@ check_data_dir(ClusterInfo *cluster)
 /*
  * check_bin_dir()
  *
- *	This function searches for the executables that we expect to find
- *	in the binaries directory.  If we find that a required executable
- *	is missing (or secured against us), we display an error message and
- *	exit().
+ *	This function searches for the executables that we expect to find in the
+ *	libexec or binaries directory.  If we find that a required executable is
+ *	missing (or secured against us), we display an error message and exit().
  */
 static void
 check_bin_dir(ClusterInfo *cluster)
@@ -375,9 +374,17 @@ check_bin_dir(ClusterInfo *cluster)
 		report_status(PG_FATAL, "\"%s\" is not a directory\n",
 					  cluster->bindir);
 
-	validate_exec(cluster->bindir, "postgres");
-	validate_exec(cluster->bindir, "pg_controldata");
-	validate_exec(cluster->bindir, "pg_ctl");
+	/*
+	 * libexec will not exist on older clusters, but if it exists, it should
+	 * be a directory.
+	 */
+	if (stat(cluster->libexecdir, &statBuf) == 0 && !S_ISDIR(statBuf.st_mode))
+		report_status(PG_FATAL, "\"%s\" is not a directory\n",
+					  cluster->libexecdir);
+
+	validate_exec(cluster, &cluster->postgres_path, "postgres");
+	validate_exec(cluster, &cluster->pg_controldata_path, "pg_controldata");
+	validate_exec(cluster, &cluster->pg_ctl_path, "pg_ctl");
 
 	/*
 	 * Fetch the binary version after checking for the existence of pg_ctl.
@@ -388,9 +395,9 @@ check_bin_dir(ClusterInfo *cluster)
 
 	/* pg_resetxlog has been renamed to pg_resetwal in version 10 */
 	if (GET_MAJOR_VERSION(cluster->bin_version) < 1000)
-		validate_exec(cluster->bindir, "pg_resetxlog");
+		validate_exec(cluster, &cluster->pg_resetwal_path, "pg_resetxlog");
 	else
-		validate_exec(cluster->bindir, "pg_resetwal");
+		validate_exec(cluster, &cluster->pg_resetwal_path, "pg_resetwal");
 
 	if (cluster == &new_cluster)
 	{
@@ -399,63 +406,81 @@ check_bin_dir(ClusterInfo *cluster)
 		 * pg_dumpall are used to dump the old cluster, but must be of the
 		 * target version.
 		 */
-		validate_exec(cluster->bindir, "initdb");
-		validate_exec(cluster->bindir, "pg_dump");
-		validate_exec(cluster->bindir, "pg_dumpall");
-		validate_exec(cluster->bindir, "pg_restore");
-		validate_exec(cluster->bindir, "psql");
-		validate_exec(cluster->bindir, "vacuumdb");
+		validate_exec(cluster, &cluster->initdb_path, "initdb");
+		validate_exec(cluster, &cluster->pg_dump_path, "pg_dump");
+		validate_exec(cluster, &cluster->pg_dumpall_path, "pg_dumpall");
+		validate_exec(cluster, &cluster->pg_restore_path, "pg_restore");
+		validate_exec(cluster, &cluster->psql_path, "psql");
+		validate_exec(cluster, &cluster->vacuumdb_path, "vacuumdb");
 	}
 }
 
-
 /*
  * validate_exec()
  *
- * validate "path" as an executable file
+ * validate "cmdName" as an executable file, either in the cluster's bindir
+ * or libexecdir (if any), and store the valid location for the command in
+ * "path" for future use.
  */
 static void
-validate_exec(const char *dir, const char *cmdName)
+validate_exec(ClusterInfo *cluster, char **path, const char *cmdName)
 {
-	char		path[MAXPGPATH];
+	int			idx;
 	struct stat buf;
+	const char *dir[] = { cluster->bindir, cluster->libexecdir, NULL };
 
-	snprintf(path, sizeof(path), "%s/%s", dir, cmdName);
+	if (cluster->libexecdir == NULL)
+		pg_fatal("libexecdir is null in validate_exec");
+
+	*path = (char *) pg_malloc0(MAXPGPATH);
+	for (idx = 0; dir[idx]; idx++)
+	{
+		snprintf(*path, MAXPGPATH, "%s/%s", dir[idx], cmdName);
 
 #ifdef WIN32
-	/* Windows requires a .exe suffix for stat() */
-	if (strlen(path) <= strlen(EXE_EXT) ||
-		pg_strcasecmp(path + strlen(path) - strlen(EXE_EXT), EXE_EXT) != 0)
-		strlcat(path, EXE_EXT, sizeof(path));
+		/* Windows requires a .exe suffix for stat() */
+		if (strlen(*path) <= strlen(EXE_EXT) ||
+			pg_strcasecmp(*path + strlen(*path) - strlen(EXE_EXT), EXE_EXT) != 0)
+			strlcat(*path, EXE_EXT, sizeof(*path));
 #endif
 
-	/*
-	 * Ensure that the file exists and is a regular file.
-	 */
-	if (stat(path, &buf) < 0)
-		pg_fatal("check for \"%s\" failed: %s\n",
-				 path, strerror(errno));
-	else if (!S_ISREG(buf.st_mode))
-		pg_fatal("check for \"%s\" failed: not a regular file\n",
-				 path);
+		/*
+		 * Ensure that the file exists and is a regular file.
+		 */
+		if (stat(*path, &buf) < 0)
+		{
+			printf("check for %s failed (dir = %s, cmd = %s)", *path, dir[idx], cmdName);
+			continue;
+		}
+		else if (!S_ISREG(buf.st_mode))
+			pg_fatal("check for \"%s\" failed: not a regular file\n",
+					 *path);
 
-	/*
-	 * Ensure that the file is both executable and readable (required for
-	 * dynamic loading).
-	 */
+		/*
+		 * Ensure that the file is both executable and readable (required for
+		 * dynamic loading).
+		 */
 #ifndef WIN32
-	if (access(path, R_OK) != 0)
+		if (access(*path, R_OK) != 0)
 #else
-	if ((buf.st_mode & S_IRUSR) == 0)
+		if ((buf.st_mode & S_IRUSR) == 0)
 #endif
-		pg_fatal("check for \"%s\" failed: cannot read file (permission denied)\n",
-				 path);
+			pg_fatal("check for \"%s\" failed: cannot read file (permission denied)\n",
+					 *path);
 
 #ifndef WIN32
-	if (access(path, X_OK) != 0)
+		if (access(*path, X_OK) != 0)
 #else
-	if ((buf.st_mode & S_IXUSR) == 0)
+		if ((buf.st_mode & S_IXUSR) == 0)
 #endif
-		pg_fatal("check for \"%s\" failed: cannot execute (permission denied)\n",
-				 path);
+			pg_fatal("check for \"%s\" failed: cannot execute (permission denied)\n",
+					 *path);
+
+		/* If we get here, all checks passed */
+		return;
+	}
+
+	/* If we get here, we failed the stat() call, and errno should be set */
+	pg_fatal("check for \"%s\" failed: %s\n",
+			 *path, strerror(errno));
 }
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index aca1ee8b48..ee245c7e02 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -43,6 +43,8 @@ parseCommandLine(int argc, char *argv[])
 		{"new-datadir", required_argument, NULL, 'D'},
 		{"old-bindir", required_argument, NULL, 'b'},
 		{"new-bindir", required_argument, NULL, 'B'},
+		{"old-libexecdir", required_argument, NULL, 'l'},
+		{"new-libexecdir", required_argument, NULL, 'L'},
 		{"old-options", required_argument, NULL, 'o'},
 		{"new-options", required_argument, NULL, 'O'},
 		{"old-port", required_argument, NULL, 'p'},
@@ -101,7 +103,7 @@ parseCommandLine(int argc, char *argv[])
 	if (os_user_effective_id == 0)
 		pg_fatal("%s: cannot be run as root\n", os_info.progname);
 
-	while ((option = getopt_long(argc, argv, "d:D:b:B:cj:ko:O:p:P:rs:U:v",
+	while ((option = getopt_long(argc, argv, "d:D:b:B:cj:kl:L:o:O:p:P:rs:U:v",
 								 long_options, &optindex)) != -1)
 	{
 		switch (option)
@@ -134,6 +136,14 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_LINK;
 				break;
 
+			case 'l':
+				old_cluster.libexecdir = pg_strdup(optarg);
+				break;
+
+			case 'L':
+				new_cluster.libexecdir = pg_strdup(optarg);
+				break;
+
 			case 'o':
 				/* append option? */
 				if (!old_cluster.pgopts)
@@ -251,6 +261,10 @@ parseCommandLine(int argc, char *argv[])
 							 "-b", _("old cluster binaries reside"), false);
 	check_required_directory(&new_cluster.bindir, "PGBINNEW", false,
 							 "-B", _("new cluster binaries reside"), true);
+	check_required_directory(&old_cluster.libexecdir, "PGLIBEXECOLD", false,
+							 "-l", _("old cluster commands reside"), false);
+	check_required_directory(&new_cluster.libexecdir, "PGLIBEXECNEW", false,
+							 "-L", _("new cluster commands reside"), true);
 	check_required_directory(&old_cluster.pgdata, "PGDATAOLD", false,
 							 "-d", _("old cluster data resides"), false);
 	check_required_directory(&new_cluster.pgdata, "PGDATANEW", false,
@@ -289,25 +303,27 @@ usage(void)
 	printf(_("Usage:\n"));
 	printf(_("  pg_upgrade [OPTION]...\n\n"));
 	printf(_("Options:\n"));
-	printf(_("  -b, --old-bindir=BINDIR       old cluster executable directory\n"));
-	printf(_("  -B, --new-bindir=BINDIR       new cluster executable directory (default\n"
-			 "                                same directory as pg_upgrade)\n"));
-	printf(_("  -c, --check                   check clusters only, don't change any data\n"));
-	printf(_("  -d, --old-datadir=DATADIR     old cluster data directory\n"));
-	printf(_("  -D, --new-datadir=DATADIR     new cluster data directory\n"));
-	printf(_("  -j, --jobs=NUM                number of simultaneous processes or threads to use\n"));
-	printf(_("  -k, --link                    link instead of copying files to new cluster\n"));
-	printf(_("  -o, --old-options=OPTIONS     old cluster options to pass to the server\n"));
-	printf(_("  -O, --new-options=OPTIONS     new cluster options to pass to the server\n"));
-	printf(_("  -p, --old-port=PORT           old cluster port number (default %d)\n"), old_cluster.port);
-	printf(_("  -P, --new-port=PORT           new cluster port number (default %d)\n"), new_cluster.port);
-	printf(_("  -r, --retain                  retain SQL and log files after success\n"));
-	printf(_("  -s, --socketdir=DIR           socket directory to use (default current dir.)\n"));
-	printf(_("  -U, --username=NAME           cluster superuser (default \"%s\")\n"), os_info.user);
-	printf(_("  -v, --verbose                 enable verbose internal logging\n"));
-	printf(_("  -V, --version                 display version information, then exit\n"));
-	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
-	printf(_("  -?, --help                    show this help, then exit\n"));
+	printf(_("  -b, --old-bindir=BINDIR         old cluster executable directory\n"));
+	printf(_("  -B, --new-bindir=BINDIR         new cluster executable directory (default\n"
+			 "                                  same directory as pg_upgrade)\n"));
+	printf(_("  -l, --old-libexecdir=LIBEXECDIR old cluster command directory\n"));
+	printf(_("  -L, --new-libexecdir=LIBEXECDIR new cluster command directory\n"));
+	printf(_("  -c, --check                     check clusters only, don't change any data\n"));
+	printf(_("  -d, --old-datadir=DATADIR       old cluster data directory\n"));
+	printf(_("  -D, --new-datadir=DATADIR       new cluster data directory\n"));
+	printf(_("  -j, --jobs=NUM                  number of simultaneous processes or threads to use\n"));
+	printf(_("  -k, --link                      link instead of copying files to new cluster\n"));
+	printf(_("  -o, --old-options=OPTIONS       old cluster options to pass to the server\n"));
+	printf(_("  -O, --new-options=OPTIONS       new cluster options to pass to the server\n"));
+	printf(_("  -p, --old-port=PORT             old cluster port number (default %d)\n"), old_cluster.port);
+	printf(_("  -P, --new-port=PORT             new cluster port number (default %d)\n"), new_cluster.port);
+	printf(_("  -r, --retain                    retain SQL and log files after success\n"));
+	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
+	printf(_("  -U, --username=NAME             cluster superuser (default \"%s\")\n"), os_info.user);
+	printf(_("  -v, --verbose                   enable verbose internal logging\n"));
+	printf(_("  -V, --version                   display version information, then exit\n"));
+	printf(_("  --clone                         clone instead of copying files to new cluster\n"));
+	printf(_("  -?, --help                      show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
 			 "  create a new database cluster (using the new version of initdb)\n"
@@ -318,22 +334,28 @@ usage(void)
 			 "  the data directory for the old cluster  (-d DATADIR)\n"
 			 "  the data directory for the new cluster  (-D DATADIR)\n"
 			 "  the \"bin\" directory for the old version (-b BINDIR)\n"
-			 "  the \"bin\" directory for the new version (-B BINDIR)\n"));
+			 "  the \"bin\" directory for the new version (-B BINDIR)\n"
+			 "  the \"libexec\" directory for the old version (-l LIBEXECDIR)\n"
+			 "  the \"libexec\" directory for the new version (-L LIBEXECDIR)\n"));
 	printf(_("\n"
 			 "For example:\n"
-			 "  pg_upgrade -d oldCluster/data -D newCluster/data -b oldCluster/bin -B newCluster/bin\n"
+			 "  pg_upgrade -d oldCluster/data -D newCluster/data -b oldCluster/bin -B newCluster/bin -l oldCluster/libexec -L newCluster/libexec\n"
 			 "or\n"));
 #ifndef WIN32
 	printf(_("  $ export PGDATAOLD=oldCluster/data\n"
 			 "  $ export PGDATANEW=newCluster/data\n"
 			 "  $ export PGBINOLD=oldCluster/bin\n"
 			 "  $ export PGBINNEW=newCluster/bin\n"
+			 "  $ export PGLIBEXECOLD=oldCluster/libexec\n"
+			 "  $ export PGLIBEXECNEW=newCluster/libexec\n"
 			 "  $ pg_upgrade\n"));
 #else
 	printf(_("  C:\\> set PGDATAOLD=oldCluster/data\n"
 			 "  C:\\> set PGDATANEW=newCluster/data\n"
 			 "  C:\\> set PGBINOLD=oldCluster/bin\n"
 			 "  C:\\> set PGBINNEW=newCluster/bin\n"
+			 "  C:\\> set PGLIBEXECOLD=oldCluster/libexec\n"
+			 "  C:\\> set PGLIBEXECNEW=newCluster/libexec\n"
 			 "  C:\\> pg_upgrade\n"));
 #endif
 	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb096..8de4710b5b 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -165,14 +165,14 @@ main(int argc, char **argv)
 	 */
 	prep_status("Setting next OID for new cluster");
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/pg_resetwal\" -o %u \"%s\"",
-			  new_cluster.bindir, old_cluster.controldata.chkpnt_nxtoid,
+			  "\"%s\" -o %u \"%s\"",
+			  new_cluster.pg_resetwal_path, old_cluster.controldata.chkpnt_nxtoid,
 			  new_cluster.pgdata);
 	check_ok();
 
 	prep_status("Sync data directory to disk");
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/initdb\" --sync-only \"%s\"", new_cluster.bindir,
+			  "\"%s\" --sync-only \"%s\"", new_cluster.initdb_path,
 			  new_cluster.pgdata);
 	check_ok();
 
@@ -208,11 +208,11 @@ setup(char *argv0, bool *live_check)
 	check_pghost_envvar();
 
 	/*
-	 * In case the user hasn't specified the directory for the new binaries
-	 * with -B, default to using the path of the currently executed pg_upgrade
-	 * binary.
+	 * In case the user hasn't specified the directory for the new commands
+	 * with -L, default to using the path of the currently executed pg_upgrade
+	 * command.
 	 */
-	if (!new_cluster.bindir)
+	if (!new_cluster.libexecdir)
 	{
 		char		exec_path[MAXPGPATH];
 
@@ -221,7 +221,22 @@ setup(char *argv0, bool *live_check)
 		/* Trim off program name and keep just path */
 		*last_dir_separator(exec_path) = '\0';
 		canonicalize_path(exec_path);
-		new_cluster.bindir = pg_strdup(exec_path);
+		new_cluster.libexecdir = pg_strdup(exec_path);
+	}
+
+	/*
+	 * In case the user hasn't specified the directory for the new binaries
+	 * with -B, construct it from the path for libexecdir.
+	 *
+	 * TODO: handle custom bin locations which are not where we expect.
+	 */
+	if (!new_cluster.bindir)
+	{
+		char		tmp[MAXPGPATH * 2];  /* room for relative path change */
+
+		snprintf(tmp, sizeof(tmp), "%s/../bin", new_cluster.libexecdir);
+		canonicalize_path(tmp);
+		new_cluster.bindir = pg_strdup(tmp);
 	}
 
 	verify_directories();
@@ -272,8 +287,8 @@ prepare_new_cluster(void)
 	 */
 	prep_status("Analyzing all rows in the new cluster");
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/vacuumdb\" %s --all --analyze %s",
-			  new_cluster.bindir, cluster_conn_opts(&new_cluster),
+			  "\"%s\" %s --all --analyze %s",
+			  new_cluster.vacuumdb_path, cluster_conn_opts(&new_cluster),
 			  log_opts.verbose ? "--verbose" : "");
 	check_ok();
 
@@ -285,8 +300,8 @@ prepare_new_cluster(void)
 	 */
 	prep_status("Freezing all rows in the new cluster");
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/vacuumdb\" %s --all --freeze %s",
-			  new_cluster.bindir, cluster_conn_opts(&new_cluster),
+			  "\"%s\" %s --all --freeze %s",
+			  new_cluster.vacuumdb_path, cluster_conn_opts(&new_cluster),
 			  log_opts.verbose ? "--verbose" : "");
 	check_ok();
 }
@@ -306,8 +321,8 @@ prepare_new_globals(void)
 	prep_status("Restoring global objects in the new cluster");
 
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/psql\" " EXEC_PSQL_ARGS " %s -f \"%s\"",
-			  new_cluster.bindir, cluster_conn_opts(&new_cluster),
+			  "\"%s\" " EXEC_PSQL_ARGS " %s -f \"%s\"",
+			  new_cluster.psql_path, cluster_conn_opts(&new_cluster),
 			  GLOBALS_DUMP_FILE);
 	check_ok();
 }
@@ -351,9 +366,9 @@ create_new_objects(void)
 				  NULL,
 				  true,
 				  true,
-				  "\"%s/pg_restore\" %s %s --exit-on-error --verbose "
+				  "\"%s\" %s %s --exit-on-error --verbose "
 				  "--dbname postgres \"%s\"",
-				  new_cluster.bindir,
+				  new_cluster.pg_restore_path,
 				  cluster_conn_opts(&new_cluster),
 				  create_opts,
 				  sql_file_name);
@@ -388,9 +403,9 @@ create_new_objects(void)
 
 		parallel_exec_prog(log_file_name,
 						   NULL,
-						   "\"%s/pg_restore\" %s %s --exit-on-error --verbose "
+						   "\"%s\" %s %s --exit-on-error --verbose "
 						   "--dbname template1 \"%s\"",
-						   new_cluster.bindir,
+						   new_cluster.pg_restore_path,
 						   cluster_conn_opts(&new_cluster),
 						   create_opts,
 						   sql_file_name);
@@ -474,17 +489,17 @@ copy_xact_xlog_xid(void)
 	/* set the next transaction id and epoch of the new cluster */
 	prep_status("Setting next transaction ID and epoch for new cluster");
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/pg_resetwal\" -f -x %u \"%s\"",
-			  new_cluster.bindir, old_cluster.controldata.chkpnt_nxtxid,
+			  "\"%s\" -f -x %u \"%s\"",
+			  new_cluster.pg_resetwal_path, old_cluster.controldata.chkpnt_nxtxid,
 			  new_cluster.pgdata);
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/pg_resetwal\" -f -e %u \"%s\"",
-			  new_cluster.bindir, old_cluster.controldata.chkpnt_nxtepoch,
+			  "\"%s\" -f -e %u \"%s\"",
+			  new_cluster.pg_resetwal_path, old_cluster.controldata.chkpnt_nxtepoch,
 			  new_cluster.pgdata);
 	/* must reset commit timestamp limits also */
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/pg_resetwal\" -f -c %u,%u \"%s\"",
-			  new_cluster.bindir,
+			  "\"%s\" -f -c %u,%u \"%s\"",
+			  new_cluster.pg_resetwal_path,
 			  old_cluster.controldata.chkpnt_nxtxid,
 			  old_cluster.controldata.chkpnt_nxtxid,
 			  new_cluster.pgdata);
@@ -509,8 +524,8 @@ copy_xact_xlog_xid(void)
 		 * counters here and the oldest multi present on system.
 		 */
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/pg_resetwal\" -O %u -m %u,%u \"%s\"",
-				  new_cluster.bindir,
+				  "\"%s\" -O %u -m %u,%u \"%s\"",
+				  new_cluster.pg_resetwal_path,
 				  old_cluster.controldata.chkpnt_nxtmxoff,
 				  old_cluster.controldata.chkpnt_nxtmulti,
 				  old_cluster.controldata.chkpnt_oldstMulti,
@@ -537,8 +552,8 @@ copy_xact_xlog_xid(void)
 		 * next=MaxMultiXactId, but multixact.c can cope with that just fine.
 		 */
 		exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-				  "\"%s/pg_resetwal\" -m %u,%u \"%s\"",
-				  new_cluster.bindir,
+				  "\"%s\" -m %u,%u \"%s\"",
+				  new_cluster.pg_resetwal_path,
 				  old_cluster.controldata.chkpnt_nxtmulti + 1,
 				  old_cluster.controldata.chkpnt_nxtmulti,
 				  new_cluster.pgdata);
@@ -549,7 +564,7 @@ copy_xact_xlog_xid(void)
 	prep_status("Resetting WAL archives");
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
 	/* use timeline 1 to match controldata and no WAL history file */
-			  "\"%s/pg_resetwal\" -l 00000001%s \"%s\"", new_cluster.bindir,
+			  "\"%s\" -l 00000001%s \"%s\"", new_cluster.pg_resetwal_path,
 			  old_cluster.controldata.nextxlogfile + 8,
 			  new_cluster.pgdata);
 	check_ok();
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cefbe0..272e461b0d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -260,6 +260,7 @@ typedef struct
 	char	   *pgconfig;		/* pathname for cluster's config file
 								 * directory */
 	char	   *bindir;			/* pathname for cluster's executable directory */
+	char	   *libexecdir;		/* pathname for cluster's command directory */
 	char	   *pgopts;			/* options to pass to the server, like pg_ctl
 								 * -o */
 	char	   *sockdir;		/* directory for Unix Domain socket, if any */
@@ -268,6 +269,18 @@ typedef struct
 	char		major_version_str[64];	/* string PG_VERSION of cluster */
 	uint32		bin_version;	/* version returned from pg_ctl */
 	const char *tablespace_suffix;	/* directory specification */
+
+	/* Validated paths to named executables */
+	char	   *postgres_path;
+	char	   *pg_controldata_path;
+	char	   *pg_ctl_path;
+	char	   *pg_resetwal_path;
+	char	   *initdb_path;
+	char	   *pg_dump_path;
+	char	   *pg_dumpall_path;
+	char	   *pg_restore_path;
+	char	   *psql_path;
+	char	   *vacuumdb_path;
 } ClusterInfo;
 
 
diff --git a/src/bin/pg_upgrade/server.c b/src/bin/pg_upgrade/server.c
index 7d17280ecb..fbe114c8bd 100644
--- a/src/bin/pg_upgrade/server.c
+++ b/src/bin/pg_upgrade/server.c
@@ -241,8 +241,8 @@ start_postmaster(ClusterInfo *cluster, bool report_and_exit_on_error)
 	 * win on ext4.
 	 */
 	snprintf(cmd, sizeof(cmd),
-			 "\"%s/pg_ctl\" -w -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
-			 cluster->bindir, SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
+			 "\"%s\" -w -l \"%s\" -D \"%s\" -o \"-p %d%s%s %s%s\" start",
+			 cluster->pg_ctl_path, SERVER_LOG_FILE, cluster->pgconfig, cluster->port,
 			 (cluster->controldata.cat_ver >=
 			  BINARY_UPGRADE_SERVER_FLAG_CAT_VER) ? " -b" :
 			 " -c autovacuum=off -c autovacuum_freeze_max_age=2000000000",
@@ -336,8 +336,8 @@ stop_postmaster(bool in_atexit)
 		return;					/* no cluster running */
 
 	exec_prog(SERVER_STOP_LOG_FILE, NULL, !in_atexit, !in_atexit,
-			  "\"%s/pg_ctl\" -w -D \"%s\" -o \"%s\" %s stop",
-			  cluster->bindir, cluster->pgconfig,
+			  "\"%s\" -w -D \"%s\" -o \"%s\" %s stop",
+			  cluster->pg_ctl_path, cluster->pgconfig,
 			  cluster->pgopts ? cluster->pgopts : "",
 			  in_atexit ? "-m fast" : "-m smart");
 
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 10a28d8133..ef3f3b32ec 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -76,6 +76,7 @@ rm -rf "$temp_root"
 mkdir "$temp_root"
 
 : ${oldbindir=$bindir}
+: ${oldlibexecdir=$libexecdir}
 
 : ${oldsrc=../../..}
 oldsrc=`cd "$oldsrc" && pwd`
@@ -86,13 +87,13 @@ newsrc=`cd ../../.. && pwd`
 # below would try to use psql from the proper installation directory
 # of the target version, which might be outdated or not exist. But
 # don't override anything else that's already in EXTRA_REGRESS_OPTS.
-EXTRA_REGRESS_OPTS="$EXTRA_REGRESS_OPTS --bindir='$oldbindir'"
+EXTRA_REGRESS_OPTS="$EXTRA_REGRESS_OPTS --bindir='$oldbindir' --libexecdir='$oldlibexecdir'"
 export EXTRA_REGRESS_OPTS
 
 # While in normal cases this will already be set up, adding bindir to
 # path allows test.sh to be invoked with different versions as
 # described in ./TESTING
-PATH=$bindir:$PATH
+PATH=$bindir:$libexecdir:$PATH
 export PATH
 
 BASE_PGDATA="$temp_root/data"
@@ -150,8 +151,8 @@ done
 EXTRA_REGRESS_OPTS="$EXTRA_REGRESS_OPTS --port=$PGPORT"
 export EXTRA_REGRESS_OPTS
 
-standard_initdb "$oldbindir"/initdb
-"$oldbindir"/pg_ctl start -l "$logdir/postmaster1.log" -o "$POSTMASTER_OPTS" -w
+standard_initdb "$oldlibexecdir"/initdb
+"$oldlibexecdir"/pg_ctl start -l "$logdir/postmaster1.log" -o "$POSTMASTER_OPTS" -w
 
 # Create databases with names covering the ASCII bytes other than NUL, BEL,
 # LF, or CR.  BEL would ring the terminal bell in the course of this test, and
@@ -204,7 +205,7 @@ if "$MAKE" -C "$oldsrc" installcheck-parallel; then
 else
 	make_installcheck_status=$?
 fi
-"$oldbindir"/pg_ctl -m fast stop
+"$oldlibexecdir"/pg_ctl -m fast stop
 if [ -n "$createdb_status" ]; then
 	exit 1
 fi
@@ -223,7 +224,7 @@ PGDATA="$BASE_PGDATA"
 
 standard_initdb 'initdb'
 
-pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "$PGDATA" -b "$oldbindir" -p "$PGPORT" -P "$PGPORT"
+pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "$PGDATA" -b "$oldbindir" -l "$oldlibexecdir" -p "$PGPORT" -P "$PGPORT"
 
 # make sure all directories and files have group permissions, on Unix hosts
 # Windows hosts don't support Unix-y permissions.
diff --git a/src/bin/pg_verifybackup/Makefile b/src/bin/pg_verifybackup/Makefile
index c07643b129..710a6a9972 100644
--- a/src/bin/pg_verifybackup/Makefile
+++ b/src/bin/pg_verifybackup/Makefile
@@ -21,13 +21,13 @@ pg_verifybackup: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
 	$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_verifybackup$(X) '$(DESTDIR)$(bindir)/pg_verifybackup$(X)'
+	$(INSTALL_PROGRAM) pg_verifybackup$(X) '$(DESTDIR)$(libexecdir)/pg_verifybackup$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_verifybackup$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_verifybackup$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_verifybackup$(X) $(OBJS)
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c
index 70b6ffdec0..6979006671 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.c
+++ b/src/bin/pg_verifybackup/pg_verifybackup.c
@@ -285,9 +285,9 @@ main(int argc, char **argv)
 		int			ret;
 
 		pg_waldump_path = pg_malloc(MAXPGPATH);
-		ret = find_other_exec(argv[0], "pg_waldump",
-							  "pg_waldump (PostgreSQL) " PG_VERSION "\n",
-							  pg_waldump_path);
+		ret = find_other_cmd(argv[0], "pg_waldump",
+							 "pg_waldump (PostgreSQL) " PG_VERSION "\n",
+							 pg_waldump_path);
 		if (ret < 0)
 		{
 			char		full_path[MAXPGPATH];
diff --git a/src/bin/pg_verifybackup/t/008_redirect.pl b/src/bin/pg_verifybackup/t/008_redirect.pl
new file mode 100644
index 0000000000..719febb3b3
--- /dev/null
+++ b/src/bin/pg_verifybackup/t/008_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'verifybackup'],
+	qr/fatal: no backup directory specified/,
+	'pg pg_verifybackup fails');
+
+command_ok(
+	['pg', 'verifybackup', '--version'],
+	'pg pg_verifybackup version ok');
+
+command_ok(
+	['pg', 'verifybackup', '--help'],
+	'pg pg_verifybackup help ok');
diff --git a/src/bin/pg_waldump/Makefile b/src/bin/pg_waldump/Makefile
index 9f333d0c8a..a8680f115d 100644
--- a/src/bin/pg_waldump/Makefile
+++ b/src/bin/pg_waldump/Makefile
@@ -33,13 +33,13 @@ $(RMGRDESCSOURCES): % : $(top_srcdir)/src/backend/access/rmgrdesc/%
 	rm -f $@ && $(LN_S) $< .
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pg_waldump$(X) '$(DESTDIR)$(bindir)/pg_waldump$(X)'
+	$(INSTALL_PROGRAM) pg_waldump$(X) '$(DESTDIR)$(libexecdir)/pg_waldump$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pg_waldump$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pg_waldump$(X)'
 
 clean distclean maintainer-clean:
 	rm -f pg_waldump$(X) $(OBJS) $(RMGRDESCSOURCES) xlogreader.c
diff --git a/src/bin/pg_waldump/t/002_redirect.pl b/src/bin/pg_waldump/t/002_redirect.pl
new file mode 100644
index 0000000000..84453df680
--- /dev/null
+++ b/src/bin/pg_waldump/t/002_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'waldump'],
+	qr/error: no arguments specified/,
+	'pg pg_waldump fails');
+
+command_ok(
+	['pg', 'waldump', '--version'],
+	'pg pg_waldump version ok');
+
+command_ok(
+	['pg', 'waldump', '--help'],
+	'pg pg_waldump help ok');
diff --git a/src/bin/pgbench/Makefile b/src/bin/pgbench/Makefile
index f402fe7b91..4b430cb53d 100644
--- a/src/bin/pgbench/Makefile
+++ b/src/bin/pgbench/Makefile
@@ -32,13 +32,13 @@ exprparse.o: exprscan.c
 distprep: exprparse.c exprscan.c
 
 install: all installdirs
-	$(INSTALL_PROGRAM) pgbench$(X) '$(DESTDIR)$(bindir)/pgbench$(X)'
+	$(INSTALL_PROGRAM) pgbench$(X) '$(DESTDIR)$(libexecdir)/pgbench$(X)'
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f '$(DESTDIR)$(bindir)/pgbench$(X)'
+	rm -f '$(DESTDIR)$(libexecdir)/pgbench$(X)'
 
 clean distclean:
 	rm -f pgbench$(X) $(OBJS)
diff --git a/src/bin/pgbench/t/003_redirect.pl b/src/bin/pgbench/t/003_redirect.pl
new file mode 100644
index 0000000000..bbc0a11e5b
--- /dev/null
+++ b/src/bin/pgbench/t/003_redirect.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 4;
+
+command_fails_like(
+	['pg', 'bench'],
+	qr/error: connection to database .* failed/,
+	'pg bench fails');
+
+command_ok(
+	['pg', 'bench', '--version'],
+	'pg bench version ok');
+
+command_ok(
+	['pg', 'bench', '--help'],
+	'pg bench help ok');
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 3302bd4dd3..b50f3b6e46 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -755,6 +755,7 @@ process_psqlrc(char *argv0)
 	char		home[MAXPGPATH];
 	char		rc_file[MAXPGPATH];
 	char		my_exec_path[MAXPGPATH];
+	char		my_rootdir[MAXPGPATH];
 	char		etc_path[MAXPGPATH];
 	char	   *envrc = getenv("PSQLRC");
 
@@ -764,7 +765,13 @@ process_psqlrc(char *argv0)
 		exit(EXIT_FAILURE);
 	}
 
-	get_etc_path(my_exec_path, etc_path);
+	if (find_my_rootdir(argv0, my_rootdir) < 0)
+	{
+		pg_log_fatal("could not find own program root directory");
+		exit(EXIT_FAILURE);
+	}
+
+	get_etc_path(my_rootdir, etc_path);
 
 	snprintf(rc_file, MAXPGPATH, "%s/%s", etc_path, SYSPSQLRC);
 	process_psqlrc_file(rc_file);
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index 42bfe475b2..047767e602 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -33,20 +33,20 @@ reindexdb: reindexdb.o common.o scripts_parallel.o $(WIN32RES) | submake-libpq s
 pg_isready: pg_isready.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
 
 install: all installdirs
-	$(INSTALL_PROGRAM) createdb$(X)   '$(DESTDIR)$(bindir)'/createdb$(X)
-	$(INSTALL_PROGRAM) dropdb$(X)     '$(DESTDIR)$(bindir)'/dropdb$(X)
-	$(INSTALL_PROGRAM) createuser$(X) '$(DESTDIR)$(bindir)'/createuser$(X)
-	$(INSTALL_PROGRAM) dropuser$(X)   '$(DESTDIR)$(bindir)'/dropuser$(X)
-	$(INSTALL_PROGRAM) clusterdb$(X)  '$(DESTDIR)$(bindir)'/clusterdb$(X)
-	$(INSTALL_PROGRAM) vacuumdb$(X)   '$(DESTDIR)$(bindir)'/vacuumdb$(X)
-	$(INSTALL_PROGRAM) reindexdb$(X)  '$(DESTDIR)$(bindir)'/reindexdb$(X)
-	$(INSTALL_PROGRAM) pg_isready$(X) '$(DESTDIR)$(bindir)'/pg_isready$(X)
+	$(INSTALL_PROGRAM) createdb$(X)   '$(DESTDIR)$(libexecdir)'/createdb$(X)
+	$(INSTALL_PROGRAM) dropdb$(X)     '$(DESTDIR)$(libexecdir)'/dropdb$(X)
+	$(INSTALL_PROGRAM) createuser$(X) '$(DESTDIR)$(libexecdir)'/createuser$(X)
+	$(INSTALL_PROGRAM) dropuser$(X)   '$(DESTDIR)$(libexecdir)'/dropuser$(X)
+	$(INSTALL_PROGRAM) clusterdb$(X)  '$(DESTDIR)$(libexecdir)'/clusterdb$(X)
+	$(INSTALL_PROGRAM) vacuumdb$(X)   '$(DESTDIR)$(libexecdir)'/vacuumdb$(X)
+	$(INSTALL_PROGRAM) reindexdb$(X)  '$(DESTDIR)$(libexecdir)'/reindexdb$(X)
+	$(INSTALL_PROGRAM) pg_isready$(X) '$(DESTDIR)$(libexecdir)'/pg_isready$(X)
 
 installdirs:
-	$(MKDIR_P) '$(DESTDIR)$(bindir)'
+	$(MKDIR_P) '$(DESTDIR)$(libexecdir)'
 
 uninstall:
-	rm -f $(addprefix '$(DESTDIR)$(bindir)'/, $(addsuffix $(X), $(PROGRAMS)))
+	rm -f $(addprefix '$(DESTDIR)$(libexecdir)'/, $(addsuffix $(X), $(PROGRAMS)))
 
 clean distclean maintainer-clean:
 	rm -f $(addsuffix $(X), $(PROGRAMS)) $(addsuffix .o, $(PROGRAMS))
diff --git a/src/common/.gitignore b/src/common/.gitignore
index ffa3284fbf..20ea8ef820 100644
--- a/src/common/.gitignore
+++ b/src/common/.gitignore
@@ -1 +1,2 @@
 /kwlist_d.h
+/pg_exec_relpath.h
diff --git a/src/common/Makefile b/src/common/Makefile
index d0be882cca..5279fe9056 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -99,6 +99,10 @@ OBJS_FRONTEND = \
 OBJS_SHLIB = $(OBJS_FRONTEND:%.o=%_shlib.o)
 OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
 
+# Generate bin and libexec relative paths
+pg_exec_relpath.h: mk_exec_relpath.pl
+	$(PERL) $(srcdir)/$^ '$(DESTDIR)$(bindir)' '$(DESTDIR)$(libexecdir)' > $@
+
 # where to find gen_keywordlist.pl and subsidiary files
 TOOLSDIR = $(top_srcdir)/src/tools
 GEN_KEYWORDLIST = $(PERL) -I $(TOOLSDIR) $(TOOLSDIR)/gen_keywordlist.pl
@@ -108,6 +112,8 @@ all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 
 distprep: kwlist_d.h
 
+exec.o: pg_exec_relpath.h
+
 # libpgcommon is needed by some contrib
 install: all installdirs
 	$(INSTALL_STLIB) libpgcommon.a '$(DESTDIR)$(libdir)/libpgcommon.a'
@@ -175,7 +181,7 @@ $(RYU_OBJS): CFLAGS += $(PERMIT_DECLARATION_AFTER_STATEMENT)
 
 # kwlist_d.h is in the distribution tarball, so it is not cleaned here.
 clean distclean:
-	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
+	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a pg_exec_relpath.h
 	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
 
 maintainer-clean: distclean
diff --git a/src/common/config_info.c b/src/common/config_info.c
index 8a5dc64ec3..44089aeff5 100644
--- a/src/common/config_info.c
+++ b/src/common/config_info.c
@@ -30,18 +30,22 @@
  * for pfreeing the result.
  */
 ConfigData *
-get_configdata(const char *my_exec_path, size_t *configdata_len)
+get_configdata(const char *my_exec_path, const char *my_rootdir,
+			   size_t *configdata_len)
 {
 	ConfigData *configdata;
 	char		path[MAXPGPATH];
 	char	   *lastsep;
 	int			i = 0;
 
+	Assert (my_rootdir != NULL);
+	Assert (my_rootdir[0] != '\0');
+
 	/* Adjust this to match the number of items filled below */
-	*configdata_len = 23;
+	*configdata_len = 24;
 	configdata = (ConfigData *) palloc(*configdata_len * sizeof(ConfigData));
 
-	configdata[i].name = pstrdup("BINDIR");
+	configdata[i].name = pstrdup("LIBEXECDIR");
 	strlcpy(path, my_exec_path, sizeof(path));
 	lastsep = strrchr(path, '/');
 	if (lastsep)
@@ -51,13 +55,13 @@ get_configdata(const char *my_exec_path, size_t *configdata_len)
 	i++;
 
 	configdata[i].name = pstrdup("DOCDIR");
-	get_doc_path(my_exec_path, path);
+	get_doc_path(my_rootdir, path);
 	cleanup_path(path);
 	configdata[i].setting = pstrdup(path);
 	i++;
 
 	configdata[i].name = pstrdup("HTMLDIR");
-	get_html_path(my_exec_path, path);
+	get_html_path(my_rootdir, path);
 	cleanup_path(path);
 	configdata[i].setting = pstrdup(path);
 	i++;
@@ -75,13 +79,19 @@ get_configdata(const char *my_exec_path, size_t *configdata_len)
 	i++;
 
 	configdata[i].name = pstrdup("INCLUDEDIR-SERVER");
-	get_includeserver_path(my_exec_path, path);
+	get_includeserver_path(my_rootdir, path);
 	cleanup_path(path);
 	configdata[i].setting = pstrdup(path);
 	i++;
 
 	configdata[i].name = pstrdup("LIBDIR");
-	get_lib_path(my_exec_path, path);
+	get_lib_path(my_rootdir, path);
+	cleanup_path(path);
+	configdata[i].setting = pstrdup(path);
+	i++;
+
+	configdata[i].name = pstrdup("BINDIR");
+	get_bin_path(my_rootdir, path);
 	cleanup_path(path);
 	configdata[i].setting = pstrdup(path);
 	i++;
@@ -99,7 +109,7 @@ get_configdata(const char *my_exec_path, size_t *configdata_len)
 	i++;
 
 	configdata[i].name = pstrdup("MANDIR");
-	get_man_path(my_exec_path, path);
+	get_man_path(my_rootdir, path);
 	cleanup_path(path);
 	configdata[i].setting = pstrdup(path);
 	i++;
@@ -111,7 +121,7 @@ get_configdata(const char *my_exec_path, size_t *configdata_len)
 	i++;
 
 	configdata[i].name = pstrdup("SYSCONFDIR");
-	get_etc_path(my_exec_path, path);
+	get_etc_path(my_rootdir, path);
 	cleanup_path(path);
 	configdata[i].setting = pstrdup(path);
 	i++;
diff --git a/src/common/exec.c b/src/common/exec.c
index f39b0a294b..90ac524ac5 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -25,6 +25,8 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include "pg_exec_relpath.h"
+
 /*
  * Hacky solution to allow expressing both frontend and backend error reports
  * in one macro call.  First argument of log_error is an errcode() call of
@@ -109,6 +111,66 @@ validate_exec(const char *path)
 	return is_x ? (is_r ? 0 : -2) : -1;
 }
 
+/*
+ * find_my_rootdir -- find an absolute path to the postgres installation
+ *
+ *  argv0 is the name passed on the command line
+ *  retpath is the output area (must be of size MAXPGPATH)
+ *  returns 0 if OK, -1 if error.
+ */
+int
+find_my_rootdir(const char *argv0, char *retpath)
+{
+	char	path[MAXPGPATH * 2];	/* room for relative path changes */
+
+	/*
+	 * Get path to my executable.  This should be somewhere in either
+	 * bindir or libexecdir, depending on who is calling.
+	 */
+	if (find_my_exec(argv0, path))
+		return -1;
+
+	/* Trim off program name and keep just directory */
+	*last_dir_separator(path) = '\0';
+	canonicalize_path(path);
+
+	/*
+	 * If our executable was in bindir or libexecdir, one of these
+	 * two will match and we can return the rootdir, otherwise we
+	 * don't know what the rootdir is and must return error.
+	 */
+	if (trim_path_suffix(path, BIN_SUFFIX, retpath) == 0)
+		return 0;
+	if (trim_path_suffix(path, LIBEXEC_SUFFIX, retpath) == 0)
+		return 0;
+	return -1;
+}
+
+int
+find_my_libexecdir(const char *argv0, char *retpath)
+{
+	/* Get the root directory based on my executable */
+	if (find_my_rootdir(argv0, retpath))
+		return -1;
+
+	/* Append the libexecdir suffix to the root directory */
+	strcpy(retpath + strlen(retpath), LIBEXEC_SUFFIX);
+	canonicalize_path(retpath);
+	return 0;
+}
+
+int
+find_my_bindir(const char *argv0, char *retpath)
+{
+	/* Get the root directory based on my executable */
+	if (find_my_rootdir(argv0, retpath))
+		return -1;
+
+	/* Append the bindir suffix to the root directory */
+	strcpy(retpath + strlen(retpath), BIN_SUFFIX);
+	canonicalize_path(retpath);
+	return 0;
+}
 
 /*
  * find_my_exec -- find an absolute path to a valid executable
@@ -314,6 +376,82 @@ resolve_symlinks(char *path)
 	return 0;
 }
 
+/*
+ * Helper function for find_other_cmd.  We have to change directories
+ * from 'path' to 'path/relpath' if and only if 'path' ends with 'suffix'.
+ * Then we check whether the target exists and is of the right version.
+ */
+static int
+check_cmd(const char *path, const char *suffix, const char *relpath,
+		  const char *target, const char *versionstr, char *retpath)
+{
+	char		cmd[MAXPGPATH];
+	char		line[MAXPGPATH];
+	int			pathlen = strlen(path);
+	int			suffixlen = strlen(suffix);
+
+	/* If our path ends with the given suffix, then we follow the relpath */
+	if (suffixlen <= pathlen && strcmp(path+pathlen-suffixlen, suffix) == 0)
+		snprintf(retpath, MAXPGPATH, "%s%s%s%s", path, relpath, target, EXE);
+	else
+		snprintf(retpath, MAXPGPATH, "%s/%s%s", path, target, EXE);
+
+	if (validate_exec(retpath) == 0)
+	{
+		snprintf(cmd, sizeof(cmd), "\"%s\" -V", retpath);
+		if (pipe_read_line(cmd, line, sizeof(line)))
+		{
+			if (strcmp(line, versionstr) != 0)
+				return -2;	/* wrong version */
+			return 0;
+		}
+	}
+
+	return -1;	/* not found */
+}
+
+
+/*
+ * Find another program and make sure it is the proper version.  Assuming our
+ * directory is either $(libexecdir) or $(bindir), this function will look in
+ * both of those directories, and in that order.  Otherwise, it will look in
+ * our directory.
+ *
+ * 'retpath' should point to memory at least MAXPGPATH in size.
+ */
+int
+find_other_cmd(const char *argv0, const char *target,
+				const char *versionstr, char *retpath)
+{
+	char		path[MAXPGPATH];
+	int			result;
+
+	if (find_my_exec(argv0, path) < 0)
+		return -1;
+
+	/* Trim off program name and keep just directory */
+	*last_dir_separator(path) = '\0';
+	canonicalize_path(path);
+
+	/*
+	 * If we are in neither the libexec nor bin directories, both attempts
+	 * below will check our current directory.  If the command is found in our
+	 * current directory, we return success (or wrong version) after just the
+	 * first attempt, and only on failure make the redundant attempt.  It
+	 * doesn't seem worth optimizing away the second attempt on failure, since
+	 * that should be rare anyway.
+	 */
+
+	/* Check libexec directory, or cwd if we're in some unexpected location */
+	result = check_cmd(path, BIN_SUFFIX, TO_LIBEXEC_RELPATH, target,
+					   versionstr, retpath);
+	if (result != -1)
+		return result;
+
+	/* Check bin directory, or cwd if we're in some unexpected location */
+	return check_cmd(path, LIBEXEC_SUFFIX, TO_BIN_RELPATH, target, versionstr,
+					 retpath);
+}
 
 /*
  * Find another program in our binary's directory,
@@ -435,6 +573,7 @@ set_pglocale_pgservice(const char *argv0, const char *app)
 {
 	char		path[MAXPGPATH];
 	char		my_exec_path[MAXPGPATH];
+	char		my_rootdir[MAXPGPATH];
 	char		env_path[MAXPGPATH + sizeof("PGSYSCONFDIR=")];	/* longer than
 																 * PGLOCALEDIR */
 	char	   *dup_path;
@@ -457,6 +596,8 @@ set_pglocale_pgservice(const char *argv0, const char *app)
 
 	if (find_my_exec(argv0, my_exec_path) < 0)
 		return;
+	if (find_my_rootdir(argv0, my_rootdir) < 0)
+		return;
 
 #ifdef ENABLE_NLS
 	get_locale_path(my_exec_path, path);
@@ -476,7 +617,7 @@ set_pglocale_pgservice(const char *argv0, const char *app)
 
 	if (getenv("PGSYSCONFDIR") == NULL)
 	{
-		get_etc_path(my_exec_path, path);
+		get_etc_path(my_rootdir, path);
 
 		/* set for libpq to use */
 		snprintf(env_path, sizeof(env_path), "PGSYSCONFDIR=%s", path);
diff --git a/src/common/mk_exec_relpath.pl b/src/common/mk_exec_relpath.pl
new file mode 100755
index 0000000000..c9ae667322
--- /dev/null
+++ b/src/common/mk_exec_relpath.pl
@@ -0,0 +1,85 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+# Some tools are located in the installation's bindir, but execute
+# commands located in the libexec directory.  Postgres's configure
+# script allows the user to override the defaults for bindir and libexecdir,
+# so we cannot assume the default that $(bindir)/../libexec is the libexecdir.
+# Nor can we assume that the $(libexec) dir from `make' will be the installed
+# path, as our commands are relocatable, and in any event they get installed
+# into a different directory for regression testing.
+#
+# We make the hopefully reasonable assumption that the relative path delta
+# between bindir and libexecdir is invariant, compute that here, and write it
+# to stdout.  The make system takes it from there.
+#
+# We are passed bindir and libexecdir on the command line.  For example,
+# C:\our\pg\foo\bin and C:\our\pg\some\other\libexecdir.  We generate a
+# relative path that can be used to cd from bindir into libexecdir.
+# For example, "../../some/other/libexecdir".  Note that we normalize to
+# unix style forward slashes.  We also generate the relative path going the
+# other way, from libexecdir back to bindir.
+#
+# TODO: What to do if the paths are windows style, and they are on differing
+# mounts?  For example, "C:\pg\bin" vs "D:\pg\libexec"
+#
+
+# Given two paths (or path suffixes) rooted at the same location, make a
+# relative path from the first to the second.
+sub make_relpath($$)
+{
+	my ($src, $dst) = @_;
+
+	# Replace path elements of src into '..', for example we would
+	# convert "foo/bin" into "../.."
+	$src =~ s{[^/]+}{..}g;
+
+	# Strip leading slash, if any, from both
+	$src =~ s{^/}{};
+	$dst =~ s{^/}{};
+
+	return join('/', $src, $dst);
+}
+
+my $bin = $ARGV[0];
+my $lib = $ARGV[1];
+
+# Create a separator that does not exist in either path argument.  We don't
+# need to be clever here.  We just keep appending spaces until we have
+# something suitable.
+my $sep = ' ';
+$sep .= ' ' while ($bin =~ m/$sep/ or $lib =~ m/$sep/);
+
+# Use a regex to find the longest common prefix of the two directories.  For
+# example, "C:\our\pg\".  We insist that the common prefix ends with a slash
+# character, such as "/" on unix or "C:\" on windows.
+#
+if ("${bin}${sep}${lib}" =~ m"^(.*[\\/])(.*?)${sep}\1(.*)$")
+{
+	# Record the suffixes of the two paths starting from where they diverge,
+	# for example, "foo\bin" and "some\other\libexecdir"
+	my ($binsuffix, $libsuffix) = ($2, $3);
+
+	# Replace windows specific directory separators in both suffixes to get,
+	# for example, "foo/bin" and "some/other/libexecdir"
+	$binsuffix =~ s{\\}{/}g;
+	$libsuffix =~ s{\\}{/}g;
+
+	# Print the two relative paths
+	printf("#ifndef PG_EXEC_RELPATH_H\n");
+	printf("#define PG_EXEC_RELPATH_H\n\n");
+	printf("#define LIBEXEC_SUFFIX \"%s\"\n", $libsuffix);
+	printf("#define BIN_SUFFIX \"%s\"\n", $binsuffix);
+	printf("#define TO_LIBEXEC_RELPATH \"/%s/\"\n",
+		make_relpath($binsuffix, $libsuffix));
+	printf("#define TO_BIN_RELPATH \"/%s/\"\n\n",
+		make_relpath($libsuffix, $binsuffix));
+	printf("#endif			/* PG_EXEC_RELPATH_H */\n");
+
+	# Successful return
+	exit 0;
+}
+warn "Cannot construct relative path for changing directories from $bin to $lib";
+exit 1;
diff --git a/src/include/common/config_info.h b/src/include/common/config_info.h
index f36b7838d0..ce24036550 100644
--- a/src/include/common/config_info.h
+++ b/src/include/common/config_info.h
@@ -16,6 +16,7 @@ typedef struct ConfigData
 } ConfigData;
 
 extern ConfigData *get_configdata(const char *my_exec_path,
+								  const char *my_rootdir,
 								  size_t *configdata_len);
 
 #endif							/* COMMON_CONFIG_INFO_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 14fa127ab1..f5cb2972c8 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -172,6 +172,7 @@ extern int	MyPMChildSlot;
 
 extern char OutputFileName[];
 extern PGDLLIMPORT char my_exec_path[];
+extern PGDLLIMPORT char my_rootdir[];
 extern char pkglib_path[];
 
 #ifdef EXEC_BACKEND
diff --git a/src/include/port.h b/src/include/port.h
index 271ff0d00b..afbcee9def 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -57,17 +57,20 @@ extern bool path_is_relative_and_below_cwd(const char *path);
 extern bool path_is_prefix_of_path(const char *path1, const char *path2);
 extern char *make_absolute_path(const char *path);
 extern const char *get_progname(const char *argv0);
+extern int trim_path_suffix(const char *path, const char *suffix, char *ret_path);
+extern void get_bin_path(const char *my_rootdir, char *ret_path);
 extern void get_share_path(const char *my_exec_path, char *ret_path);
-extern void get_etc_path(const char *my_exec_path, char *ret_path);
+extern void get_etc_path(const char *my_rootdir, char *ret_path);
 extern void get_include_path(const char *my_exec_path, char *ret_path);
 extern void get_pkginclude_path(const char *my_exec_path, char *ret_path);
-extern void get_includeserver_path(const char *my_exec_path, char *ret_path);
-extern void get_lib_path(const char *my_exec_path, char *ret_path);
+extern void get_includeserver_path(const char *my_rootdir, char *ret_path);
+extern void get_lib_path(const char *my_rootdir, char *ret_path);
+extern void get_libexec_path(const char *my_rootdir, char *ret_path);
 extern void get_pkglib_path(const char *my_exec_path, char *ret_path);
 extern void get_locale_path(const char *my_exec_path, char *ret_path);
-extern void get_doc_path(const char *my_exec_path, char *ret_path);
-extern void get_html_path(const char *my_exec_path, char *ret_path);
-extern void get_man_path(const char *my_exec_path, char *ret_path);
+extern void get_doc_path(const char *my_rootdir, char *ret_path);
+extern void get_html_path(const char *my_rootdir, char *ret_path);
+extern void get_man_path(const char *my_rootdir, char *ret_path);
 extern bool get_home_path(char *ret_path);
 extern void get_parent_directory(char *path);
 
@@ -103,9 +106,14 @@ extern void pgfnames_cleanup(char **filenames);
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
 
 /* Portable way to find and execute binaries (in exec.c) */
+extern int	find_my_rootdir(const char *argv0, char *retpath);
+extern int	find_my_libexecdir(const char *argv0, char *retpath);
+extern int	find_my_bindir(const char *argv0, char *retpath);
 extern int	find_my_exec(const char *argv0, char *retpath);
 extern int	find_other_exec(const char *argv0, const char *target,
 							const char *versionstr, char *retpath);
+extern int	find_other_cmd(const char *argv0, const char *target,
+						   const char *versionstr, char *retpath);
 extern char *pipe_read_line(char *cmd, char *line, int maxsize);
 
 /* Doesn't belong here, but this is used with find_other_exec(), so... */
diff --git a/src/interfaces/ecpg/test/Makefile b/src/interfaces/ecpg/test/Makefile
index be53b7b94d..fe337c91e1 100644
--- a/src/interfaces/ecpg/test/Makefile
+++ b/src/interfaces/ecpg/test/Makefile
@@ -80,14 +80,14 @@ endif
 REGRESS_OPTS = --dbname=ecpg1_regression,ecpg2_regression --create-role=regress_ecpg_user1,regress_ecpg_user2 $(EXTRA_REGRESS_OPTS)
 
 check: all
-	$(with_temp_install) ./pg_regress $(REGRESS_OPTS) --temp-instance=./tmp_check $(TEMP_CONF) --bindir= $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule sql/twophase
+	$(with_temp_install) ./pg_regress $(REGRESS_OPTS) --temp-instance=./tmp_check $(TEMP_CONF) --bindir= --libexecdir= $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule sql/twophase
 
 # Connect to the server using TCP, and add a TCP-specific test.
 checktcp: all | temp-install
-	$(with_temp_install) ./pg_regress $(REGRESS_OPTS) --temp-instance=./tmp_check $(TEMP_CONF) --bindir= $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule --host=localhost sql/twophase connect/test1
+	$(with_temp_install) ./pg_regress $(REGRESS_OPTS) --temp-instance=./tmp_check $(TEMP_CONF) --bindir= --libexecdir= $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule --host=localhost sql/twophase connect/test1
 
 installcheck: all
-	./pg_regress $(REGRESS_OPTS) --bindir='$(bindir)' $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule
+	./pg_regress $(REGRESS_OPTS) --bindir='$(bindir)' --libexecdir='$(libexecdir)'$(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule
 
 # Versions of the check tests that include the twophase commit test.
 # It only makes sense to run these if set up to use prepared transactions,
@@ -95,7 +95,7 @@ installcheck: all
 # installcheck case.
 
 installcheck-prepared-txns: all
-	./pg_regress $(REGRESS_OPTS) --bindir='$(bindir)' $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule sql/twophase
+	./pg_regress $(REGRESS_OPTS) --bindir='$(bindir)' --libexecdir='$(libexecdir)'$(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule sql/twophase
 
 check-prepared-txns: all | temp-install
-	$(with_temp_install) ./pg_regress $(REGRESS_OPTS) --temp-instance=./tmp_check $(TEMP_CONF) --bindir= $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule sql/twophase
+	$(with_temp_install) ./pg_regress $(REGRESS_OPTS) --temp-instance=./tmp_check $(TEMP_CONF) --bindir= --libexecdir= $(pg_regress_locale_flags) $(THREAD) --schedule=$(srcdir)/ecpg_schedule sql/twophase
diff --git a/src/port/Makefile b/src/port/Makefile
index 8defa1257b..ce23e9ed85 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -145,6 +145,7 @@ path_srv.o: path.c pg_config_paths.h
 # available to configure.
 pg_config_paths.h: $(top_builddir)/src/Makefile.global
 	echo "#define PGBINDIR \"$(bindir)\"" >$@
+	echo "#define PGLIBEXECDIR \"$(libexecdir)\"" >>$@
 	echo "#define PGSHAREDIR \"$(datadir)\"" >>$@
 	echo "#define SYSCONFDIR \"$(sysconfdir)\"" >>$@
 	echo "#define INCLUDEDIR \"$(includedir)\"" >>$@
diff --git a/src/port/path.c b/src/port/path.c
index 10e87fbae8..88defa3080 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -48,6 +48,8 @@ static void make_relative_path(char *ret_path, const char *target_path,
 							   const char *bin_path, const char *my_exec_path);
 static void trim_directory(char *path);
 static void trim_trailing_separator(char *path);
+static void append_path_suffix(char *ret_path, const char *rootpath,
+							   const char *suffix);
 
 
 /*
@@ -588,6 +590,65 @@ no_match:
 	canonicalize_path(ret_path);
 }
 
+void
+append_path_suffix(char *ret_path, const char *rootpath, const char *suffix)
+{
+	char	buffer[MAXPGPATH*2];
+
+	snprintf(buffer, MAXPGPATH*2, "%s/%s", rootpath, suffix);
+	canonicalize_path(buffer);
+	strlcpy(ret_path, buffer, MAXPGPATH);
+}
+
+/*
+ * trim_path_suffix
+ *
+ * If the given suffix matches the end of the path, store the prefix
+ * in ret_path.  Returns zero on success, -1 if the suffix does not match.
+ * Note that matching is done on whole path components, such that
+ * "cd/ef" is a suffix of "ab/cd/ef" but not a suffix of "abcd/ef".
+ *
+ * ret_path must be a buffer at least MAXPGPATH in length.
+ */
+int
+trim_path_suffix(const char *path, const char *suffix, char *ret_path)
+{
+	int		pathlen = strlen(path);
+	int		suffixlen = strlen(suffix);
+	int		tail_start;
+
+	if (suffixlen > pathlen)
+		return -1;
+
+	/*
+	 * We consider a path to be a suffix of itself, though this does
+	 * result in a zero-length prefix in the ret_path.
+	 */
+	if (suffixlen == pathlen)
+	{
+		if (dir_strcmp(path, suffix))
+			return -1;
+		ret_path[0] = '\0';
+		return 0;
+	}
+
+	/*
+	 * Tail match?
+	 */
+	tail_start = pathlen - suffixlen;
+	if ((IS_DIR_SEP(path[tail_start - 1]) ||
+		 (IS_DIR_SEP(path[tail_start]) && IS_DIR_SEP(suffix[0]))) &&
+		dir_strcmp(path + tail_start, suffix) == 0)
+	{
+		strncpy(ret_path, path, tail_start);
+		ret_path[tail_start] = '\0';
+		canonicalize_path(ret_path);
+		return 0;
+	}
+
+	return -1;
+}
+
 
 /*
  * make_absolute_path
@@ -697,6 +758,15 @@ make_absolute_path(const char *path)
 }
 
 
+/*
+ *	get_bin_path
+ */
+void
+get_bin_path(const char *my_rootdir, char *ret_path)
+{
+	append_path_suffix(ret_path, my_rootdir, PGBINDIR);
+}
+
 /*
  *	get_share_path
  */
@@ -710,9 +780,9 @@ get_share_path(const char *my_exec_path, char *ret_path)
  *	get_etc_path
  */
 void
-get_etc_path(const char *my_exec_path, char *ret_path)
+get_etc_path(const char *my_rootdir, char *ret_path)
 {
-	make_relative_path(ret_path, SYSCONFDIR, PGBINDIR, my_exec_path);
+	append_path_suffix(ret_path, my_rootdir, SYSCONFDIR);
 }
 
 /*
@@ -737,18 +807,27 @@ get_pkginclude_path(const char *my_exec_path, char *ret_path)
  *	get_includeserver_path
  */
 void
-get_includeserver_path(const char *my_exec_path, char *ret_path)
+get_includeserver_path(const char *my_rootdir, char *ret_path)
 {
-	make_relative_path(ret_path, INCLUDEDIRSERVER, PGBINDIR, my_exec_path);
+	append_path_suffix(ret_path, my_rootdir, INCLUDEDIRSERVER);
 }
 
 /*
  *	get_lib_path
  */
 void
-get_lib_path(const char *my_exec_path, char *ret_path)
+get_lib_path(const char *my_rootdir, char *ret_path)
+{
+	append_path_suffix(ret_path, my_rootdir, LIBDIR);
+}
+
+/*
+ *	get_libexec_path
+ */
+void
+get_libexec_path(const char *my_rootdir, char *ret_path)
 {
-	make_relative_path(ret_path, LIBDIR, PGBINDIR, my_exec_path);
+	append_path_suffix(ret_path, my_rootdir, PGLIBEXECDIR);
 }
 
 /*
@@ -773,27 +852,27 @@ get_locale_path(const char *my_exec_path, char *ret_path)
  *	get_doc_path
  */
 void
-get_doc_path(const char *my_exec_path, char *ret_path)
+get_doc_path(const char *my_rootdir, char *ret_path)
 {
-	make_relative_path(ret_path, DOCDIR, PGBINDIR, my_exec_path);
+	append_path_suffix(ret_path, my_rootdir, DOCDIR);
 }
 
 /*
  *	get_html_path
  */
 void
-get_html_path(const char *my_exec_path, char *ret_path)
+get_html_path(const char *my_rootdir, char *ret_path)
 {
-	make_relative_path(ret_path, HTMLDIR, PGBINDIR, my_exec_path);
+	append_path_suffix(ret_path, my_rootdir, HTMLDIR);
 }
 
 /*
  *	get_man_path
  */
 void
-get_man_path(const char *my_exec_path, char *ret_path)
+get_man_path(const char *my_rootdir, char *ret_path)
 {
-	make_relative_path(ret_path, MANDIR, PGBINDIR, my_exec_path);
+	append_path_suffix(ret_path, my_rootdir, MANDIR);
 }
 
 
diff --git a/src/test/isolation/isolation_main.c b/src/test/isolation/isolation_main.c
index 50916b00dc..8006518543 100644
--- a/src/test/isolation/isolation_main.c
+++ b/src/test/isolation/isolation_main.c
@@ -42,8 +42,8 @@ isolation_start_test(const char *testname,
 	if (!looked_up_isolation_exec)
 	{
 		/* look for isolationtester binary */
-		if (find_other_exec(saved_argv0, "isolationtester",
-							PG_ISOLATION_VERSIONSTR, isolation_exec) != 0)
+		if (find_other_cmd(saved_argv0, "isolationtester",
+						   PG_ISOLATION_VERSIONSTR, isolation_exec) != 0)
 		{
 			fprintf(stderr, _("could not find proper isolationtester binary\n"));
 			exit(2);
@@ -122,11 +122,11 @@ isolation_init(int argc, char **argv)
 	size_t		argv0_len;
 
 	/*
-	 * We unfortunately cannot do the find_other_exec() lookup to find the
+	 * We unfortunately cannot do the find_other_cmd() lookup to find the
 	 * "isolationtester" binary here.  regression_main() calls the
 	 * initialization functions before parsing the commandline arguments and
 	 * thus hasn't changed the library search path at this point which in turn
-	 * can cause the "isolationtester -V" invocation that find_other_exec()
+	 * can cause the "isolationtester -V" invocation that find_other_cmd()
 	 * does to fail since it's linked to libpq.  So we instead copy argv[0]
 	 * and do the lookup the first time through isolation_start_test().
 	 */
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index 38b2b1e8e1..fb4fba6695 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -75,6 +75,7 @@ bool		debug = false;
 char	   *inputdir = ".";
 char	   *outputdir = ".";
 char	   *bindir = PGBINDIR;
+char	   *libexecdir = PGLIBEXECDIR;
 char	   *launcher = NULL;
 static _stringlist *loadextension = NULL;
 static int	max_connections = 0;
@@ -269,8 +270,8 @@ stop_postmaster(void)
 
 		snprintf(buf, sizeof(buf),
 				 "\"%s%spg_ctl\" stop -D \"%s/data\" -s",
-				 bindir ? bindir : "",
-				 bindir ? "/" : "",
+				 libexecdir ? libexecdir : "",
+				 libexecdir ? "/" : "",
 				 temp_instance);
 		r = system(buf);
 		if (r != 0)
@@ -2037,6 +2038,8 @@ help(void)
 	printf(_("Options:\n"));
 	printf(_("      --bindir=BINPATH          use BINPATH for programs that are run;\n"));
 	printf(_("                                if empty, use PATH from the environment\n"));
+	printf(_("      --libexecdir=LIBEXECPATH  use LIBEXECPATH for commands that are run;\n"));
+	printf(_("                                if empty, use PATH from the environment\n"));
 	printf(_("      --config-auth=DATADIR     update authentication settings for DATADIR\n"));
 	printf(_("      --create-role=ROLE        create the specified role before testing\n"));
 	printf(_("      --dbname=DB               use database DB (default \"regression\")\n"));
@@ -2095,12 +2098,13 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		{"port", required_argument, NULL, 14},
 		{"user", required_argument, NULL, 15},
 		{"bindir", required_argument, NULL, 16},
-		{"dlpath", required_argument, NULL, 17},
-		{"create-role", required_argument, NULL, 18},
-		{"temp-config", required_argument, NULL, 19},
-		{"use-existing", no_argument, NULL, 20},
+		{"libexecdir", required_argument, NULL, 17},
+		{"dlpath", required_argument, NULL, 18},
+		{"create-role", required_argument, NULL, 19},
+		{"temp-config", required_argument, NULL, 20},
+		{"use-existing", no_argument, NULL, 21},
 		{"launcher", required_argument, NULL, 21},
-		{"load-extension", required_argument, NULL, 22},
+		{"load-extension", required_argument, NULL, 23},
 		{"config-auth", required_argument, NULL, 24},
 		{"max-concurrent-tests", required_argument, NULL, 25},
 		{NULL, 0, NULL, 0}
@@ -2209,21 +2213,28 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 					bindir = NULL;
 				break;
 			case 17:
-				dlpath = pg_strdup(optarg);
+				/* "--libexecdir=" means to use PATH */
+				if (strlen(optarg))
+					libexecdir = pg_strdup(optarg);
+				else
+					libexecdir = NULL;
 				break;
 			case 18:
-				split_to_stringlist(optarg, ",", &extraroles);
+				dlpath = pg_strdup(optarg);
 				break;
 			case 19:
-				add_stringlist_item(&temp_configs, optarg);
+				split_to_stringlist(optarg, ",", &extraroles);
 				break;
 			case 20:
-				use_existing = true;
+				add_stringlist_item(&temp_configs, optarg);
 				break;
 			case 21:
-				launcher = pg_strdup(optarg);
+				use_existing = true;
 				break;
 			case 22:
+				launcher = pg_strdup(optarg);
+				break;
+			case 23:
 				add_stringlist_item(&loadextension, optarg);
 				break;
 			case 24:
@@ -2319,8 +2330,8 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc
 		header(_("initializing database system"));
 		snprintf(buf, sizeof(buf),
 				 "\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
-				 bindir ? bindir : "",
-				 bindir ? "/" : "",
+				 libexecdir ? libexecdir : "",
+				 libexecdir ? "/" : "",
 				 temp_instance,
 				 debug ? " --debug" : "",
 				 nolocale ? " --no-locale" : "",
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index a13ca6e02e..b43ad7d38a 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -734,6 +734,7 @@ sub GenerateFiles
 		  || confess "Could not open pg_config_paths.h";
 		print $o <<EOF;
 #define PGBINDIR "/bin"
+#define PGLIBEXECDIR "/libexec"
 #define PGSHAREDIR "/share"
 #define SYSCONFDIR "/etc"
 #define INCLUDEDIR "/include"
diff --git a/src/tools/msvc/vcregress.pl b/src/tools/msvc/vcregress.pl
index 0a98f6e37d..e87c636455 100644
--- a/src/tools/msvc/vcregress.pl
+++ b/src/tools/msvc/vcregress.pl
@@ -136,6 +136,7 @@ sub check
 		"../../../$Config/pg_regress/pg_regress",
 		"--dlpath=.",
 		"--bindir=",
+		"--libexecdir=",
 		"--schedule=${schedule}_schedule",
 		"--max-concurrent-tests=20",
 		"--encoding=SQL_ASCII",
@@ -162,6 +163,7 @@ sub ecpgcheck
 	my @args     = (
 		"../../../../$Config/pg_regress_ecpg/pg_regress_ecpg",
 		"--bindir=",
+		"--libexecdir=",
 		"--dbname=ecpg1_regression,ecpg2_regression",
 		"--create-role=regress_ecpg_user1,regress_ecpg_user2",
 		"--schedule=${schedule}_schedule",
-- 
2.21.1 (Apple Git-122.3)

