New 'pg' consolidated metacommand patch

Started by Mark Dilgerover 5 years ago18 messages
#1Mark Dilger
mark.dilger@enterprisedb.com
1 attachment(s)

Hackers,

Attached is a patch for a `pg' command that consolidates various PostgreSQL functionality into a single command, along the lines of how `git' commands are run from a single 'git' executable. In other words,

`pg upgrade` # instead of `pg_upgrade`
`pg resetwal` # instead of `pg_resetwal`

This has been discussed before on the -hackers list, but I don't recall seeing a patch. I'm submitting this patch mostly as a way of moving the conversation along, fully expecting the community to want some (or all) of what I wrote to be changed.

I'd also appreciate +1 and -1 votes on the overall idea, in case this entire feature, regardless of implementation, is simply something the community does not want.

Once again, this is mostly intended as a starting point for discussion.

The patch moves some commands from BINDIR to LIBEXECDIR where `pg' expects to find them. For commands named pg_foo, the executable is still named pg_foo and the sources are still located in src/bin/pg_foo/, but the command can now be run as `pg foo`, `pg foo --version`, `pg foo FOO SPECIFIC ARGS`, etc.

The command pgbench (no underscore) maps to 'pg bench'.

Commands without a "pg" prefix stay the same, so "createdb" => "pg createdb", etc.

The 'psql' and 'postgres' executables (and the 'postmaster' link) have been left in BINDIR, as has 'ecpg'. The 'pg' executable has been added to BINDIR.

All other executables have been moved to LIBEXECDIR where they retain their old names and can still be run directly from the command line. If we committed this patch for v14, I think it makes sense that packagers could put the LIBEXECDIR in the PATH so that 3rd-party scripts which call pg_ctl, initdb, etc. continue to work. For that reason, I did not change the names of the executables, merely their location. During conversations with Robert off-list, we discussed renaming the executables to things like 'pg-ctl' (hyphen rather than underscore), mostly because that's the more modern way of doing it and follows what 'git' does. To avoid breaking scripts that execute these commands by the old name, this patch doesn't go that far. It also leaves the usage() functions alone such that when they report their own progname in the usage text, they do so under the old name. This would need to change at some point, but I'm unclear on whether that would be for v14 or if it would be delayed.

The binaries 'createuser' and 'dropuser' might be better named 'createrole' and 'droprole'. I don't currently have aliases in this patch, but it might make sense to allow 'pg createrole' as a synonym for 'pg createuser' and 'pg droprole' as a synonym for 'pg dropuser'. I have not pursued that yet, largely because as soon as you go that route, it starts making sense to have things like 'pg drop user', 'pg cluster db' and so forth, with the extra spaces. How far would people want me to go in this direction?

Prior to this patch, postgres binaries that need to execute other postgres binaries determine the BINDIR using find_my_exec and trimming off their own executable name. They can then assume the other binary is in that same directory. After this patch, binaries need to find the common prefix ROOTDIR = commonprefix(BINDIR,LIBEXECDIR) and then assume the other binary is either in ROOTDIR/binsuffix or ROOTDIR/libexecsuffix. This may cause problems on windows if BINDIR and LIBEXECDIR are configured on different drives, as there won't be a common prefix of C:\my\pg\bin and D:\my\pg\libexec. I'm hoping somebody with more Windows savvy expresses an opinion about how to handle this.

The handling of the old libexec directory in pg_upgrade is not as robust as it could be. I'll look to improve that for a subsequent version of the patch, assuming the overall idea of the patch seems acceptable.

I've updated some of the doc/sgml/* files, but don't want to spend too much time changing documentation until we have some consensus that the patch is moving in the right direction.

Attachments:

v1-0001-Implementing-new-pg-consolidated-metacommand.patchapplication/octet-stream; name=v1-0001-Implementing-new-pg-consolidated-metacommand.patch; x-unix-mode=0644Download
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)

#2David G. Johnston
david.g.johnston@gmail.com
In reply to: Mark Dilger (#1)
Re: New 'pg' consolidated metacommand patch

On Tue, May 26, 2020 at 4:19 PM Mark Dilger <mark.dilger@enterprisedb.com>
wrote:

I'd also appreciate +1 and -1 votes on the overall idea, in case this
entire feature, regardless of implementation, is simply something the
community does not want.

-1, at least as part of core. My question would be how much of this is
would be needed if someone were to create an external project that
installed a "pg" command on top of an existing PostgreSQL installation. Or
put differently, how many of the changes to the existing binaries are
required versus nice-to-have?

David J.

#3Dave Page
dpage@pgadmin.org
In reply to: Mark Dilger (#1)
Re: New 'pg' consolidated metacommand patch

Hi

On Wed, May 27, 2020 at 12:19 AM Mark Dilger <mark.dilger@enterprisedb.com>
wrote:

I think it makes sense that packagers could put the LIBEXECDIR in the PATH
so that 3rd-party scripts which call pg_ctl, initdb, etc. continue to
work.

Having packages that futz with the PATH is generally a bad idea, especially
those that support side-by-side installations of different versions. None
of ours (EDBs) will be doing so.

--
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#4Magnus Hagander
magnus@hagander.net
In reply to: Mark Dilger (#1)
Re: New 'pg' consolidated metacommand patch

On Wed, May 27, 2020 at 1:19 AM Mark Dilger <mark.dilger@enterprisedb.com>
wrote:

Hackers,

Attached is a patch for a `pg' command that consolidates various
PostgreSQL functionality into a single command, along the lines of how
`git' commands are run from a single 'git' executable. In other words,

`pg upgrade` # instead of `pg_upgrade`
`pg resetwal` # instead of `pg_resetwal`

This has been discussed before on the -hackers list, but I don't recall
seeing a patch. I'm submitting this patch mostly as a way of moving the
conversation along, fully expecting the community to want some (or all) of
what I wrote to be changed.

As mentioned at least once before, the "pg" name is already taken in posix.
Granted it has been removed now, but it was removed from posix in 2018,
which I think is nowhere near soon enough to "steal. See for example
https://en.wikipedia.org/wiki/Pg_(Unix)

All other executables have been moved to LIBEXECDIR where they retain their

old names and can still be run directly from the command line. If we
committed this patch for v14, I think it makes sense that packagers could
put the LIBEXECDIR in the PATH so that 3rd-party scripts which call pg_ctl,
initdb, etc. continue to work.

I would definitely not expect a packager to change the PATH, as also
mentioned by others. More likely options would be to symlink the binaries
into the actual bindir, or just set both those directories to the same one
(in the path) for a number of releases as a transition.

But you should definitely poll the packagers separately to make sure
something is done that works well for them -- especially when it comes to
integrating with for example the debian/ubuntu wrapper system that already
supports multiple parallel installs. And mind that they don't typically
follow hackers actively (I think), so it would be worthwhile to bring their
attention specifically to the thread. In many ways I'd find them more
important to get input from than most "other hackers" :)

For that reason, I did not change the names of the executables, merely
their location. During conversations with Robert off-list, we discussed
renaming the executables to things like 'pg-ctl' (hyphen rather than
underscore), mostly because that's the more modern way of doing it and
follows what 'git' does. To avoid breaking scripts that execute these
commands by the old name, this patch doesn't go that far. It also leaves
the usage() functions alone such that when they report their own progname
in the usage text, they do so under the old name. This would need to
change at some point, but I'm unclear on whether that would be for v14 or
if it would be delayed.

Ugh, yeah, please don't do that. Renaming them just to make it "look more
modern" helps nobody, really. Especially if the suggestion is people should
be using the shared-launcher binary anyway.

usage() seems more reasonable to change as part of a patch like this.

The binaries 'createuser' and 'dropuser' might be better named 'createrole'

and 'droprole'. I don't currently have aliases in this patch, but it might
make sense to allow 'pg createrole' as a synonym for 'pg createuser' and
'pg droprole' as a synonym for 'pg dropuser'. I have not pursued that yet,
largely because as soon as you go that route, it starts making sense to
have things like 'pg drop user', 'pg cluster db' and so forth, with the
extra spaces. How far would people want me to go in this direction?

I'd say a createrole would make sense, but certainly not a "create role".
You'd end up with unlimited number of commands. But in either of them, I'd
say keep aliases completely out of it for a first iteration.

Prior to this patch, postgres binaries that need to execute other postgres

binaries determine the BINDIR using find_my_exec and trimming off their own
executable name. They can then assume the other binary is in that same
directory. After this patch, binaries need to find the common prefix
ROOTDIR = commonprefix(BINDIR,LIBEXECDIR) and then assume the other binary
is either in ROOTDIR/binsuffix or ROOTDIR/libexecsuffix. This may cause
problems on windows if BINDIR and LIBEXECDIR are configured on different
drives, as there won't be a common prefix of C:\my\pg\bin and
D:\my\pg\libexec. I'm hoping somebody with more Windows savvy expresses an
opinion about how to handle this.

Maybe the "pg" binary could just pass down it's own location as a parameter
to the binary it calls, thereby making sure that binary has direct access
to both?

--
Magnus Hagander
Me: https://www.hagander.net/ <http://www.hagander.net/&gt;
Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/&gt;

#5Mark Dilger
mark.dilger@enterprisedb.com
In reply to: David G. Johnston (#2)
Re: New 'pg' consolidated metacommand patch

On May 26, 2020, at 4:59 PM, David G. Johnston <david.g.johnston@gmail.com> wrote:

On Tue, May 26, 2020 at 4:19 PM Mark Dilger <mark.dilger@enterprisedb.com> wrote:
I'd also appreciate +1 and -1 votes on the overall idea, in case this entire feature, regardless of implementation, is simply something the community does not want.

-1, at least as part of core. My question would be how much of this is would be needed if someone were to create an external project that installed a "pg" command on top of an existing PostgreSQL installation. Or put differently, how many of the changes to the existing binaries are required versus nice-to-have?

If the only goal of something like this were to have a frontend that could execute the various postgres binaries, then I'd say no changes to those binaries would be needed, and the frontend would not be worth very much. The value in having the frontend is that it makes it less difficult to introduce new commands to the postgres suite of commands, as you don't need to worry about whether another executable by the same name might happen to already exist somewhere. Even introducing a command named "pg" has already gotten such a response on this thread. By having the commands installed in postgres's libexec rather than bin, you can put whatever commands you want in libexec without worrying about conflicts. That still leaves open the question of whether existing commands get moved into libexec, and if so, if they keep the same name. An external project for this would be worthless in this regard, as the community wouldn't get any benefit when debating the merits of introducing a new command vs. the potential for conflicts.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#6Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Dave Page (#3)
Re: New 'pg' consolidated metacommand patch

On May 27, 2020, at 1:13 AM, Dave Page <dpage@pgadmin.org> wrote:

Hi

On Wed, May 27, 2020 at 12:19 AM Mark Dilger <mark.dilger@enterprisedb.com> wrote:

I think it makes sense that packagers could put the LIBEXECDIR in the PATH so that 3rd-party scripts which call pg_ctl, initdb, etc. continue to work.

Having packages that futz with the PATH is generally a bad idea, especially those that support side-by-side installations of different versions. None of ours (EDBs) will be doing so.

I probably phrased that badly. The operative word in that sentence was "could". If we rename the binaries, people can still make links to them from the old name, but if we don't rename them, then either links or PATH changes *could* be used. I'm not trying to recommend any particular approach. Mentioning "packagers" probably wasn't helpful, as "people" works just as well in that sentence.

There is also the option of not moving the binaries at all, and only putting new commands into libexec, while grandfathering existing ones in bin.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#7Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Magnus Hagander (#4)
Re: New 'pg' consolidated metacommand patch

On May 27, 2020, at 1:50 AM, Magnus Hagander <magnus@hagander.net> wrote:

On Wed, May 27, 2020 at 1:19 AM Mark Dilger <mark.dilger@enterprisedb.com> wrote:
Hackers,

Attached is a patch for a `pg' command that consolidates various PostgreSQL functionality into a single command, along the lines of how `git' commands are run from a single 'git' executable. In other words,

`pg upgrade` # instead of `pg_upgrade`
`pg resetwal` # instead of `pg_resetwal`

This has been discussed before on the -hackers list, but I don't recall seeing a patch. I'm submitting this patch mostly as a way of moving the conversation along, fully expecting the community to want some (or all) of what I wrote to be changed.

As mentioned at least once before, the "pg" name is already taken in posix. Granted it has been removed now, but it was removed from posix in 2018, which I think is nowhere near soon enough to "steal. See for example https://en.wikipedia.org/wiki/Pg_(Unix)

Care to recommend a different name?

All other executables have been moved to LIBEXECDIR where they retain their old names and can still be run directly from the command line. If we committed this patch for v14, I think it makes sense that packagers could put the LIBEXECDIR in the PATH so that 3rd-party scripts which call pg_ctl, initdb, etc. continue to work.

I would definitely not expect a packager to change the PATH, as also mentioned by others. More likely options would be to symlink the binaries into the actual bindir, or just set both those directories to the same one (in the path) for a number of releases as a transition.

There is nothing in the patch that expects packagers to muck with the PATH. The idea, badly phrased, was that by keeping the names of the executables and only changing locations, people would have more options for how to deal with the change.

But you should definitely poll the packagers separately to make sure something is done that works well for them -- especially when it comes to integrating with for example the debian/ubuntu wrapper system that already supports multiple parallel installs. And mind that they don't typically follow hackers actively (I think), so it would be worthwhile to bring their attention specifically to the thread. In many ways I'd find them more important to get input from than most "other hackers" :)

Yeah, good advice. Since I've already floated this on -hackers, I might wait a few days for comment, then if it looks encouraging, ask on other lists.

For that reason, I did not change the names of the executables, merely their location. During conversations with Robert off-list, we discussed renaming the executables to things like 'pg-ctl' (hyphen rather than underscore), mostly because that's the more modern way of doing it and follows what 'git' does. To avoid breaking scripts that execute these commands by the old name, this patch doesn't go that far. It also leaves the usage() functions alone such that when they report their own progname in the usage text, they do so under the old name. This would need to change at some point, but I'm unclear on whether that would be for v14 or if it would be delayed.

Ugh, yeah, please don't do that. Renaming them just to make it "look more modern" helps nobody, really. Especially if the suggestion is people should be using the shared-launcher binary anyway.

usage() seems more reasonable to change as part of a patch like this.

The binaries 'createuser' and 'dropuser' might be better named 'createrole' and 'droprole'. I don't currently have aliases in this patch, but it might make sense to allow 'pg createrole' as a synonym for 'pg createuser' and 'pg droprole' as a synonym for 'pg dropuser'. I have not pursued that yet, largely because as soon as you go that route, it starts making sense to have things like 'pg drop user', 'pg cluster db' and so forth, with the extra spaces. How far would people want me to go in this direction?

I'd say a createrole would make sense, but certainly not a "create role". You'd end up with unlimited number of commands. But in either of them, I'd say keep aliases completely out of it for a first iteration.

Ok.

Prior to this patch, postgres binaries that need to execute other postgres binaries determine the BINDIR using find_my_exec and trimming off their own executable name. They can then assume the other binary is in that same directory. After this patch, binaries need to find the common prefix ROOTDIR = commonprefix(BINDIR,LIBEXECDIR) and then assume the other binary is either in ROOTDIR/binsuffix or ROOTDIR/libexecsuffix. This may cause problems on windows if BINDIR and LIBEXECDIR are configured on different drives, as there won't be a common prefix of C:\my\pg\bin and D:\my\pg\libexec. I'm hoping somebody with more Windows savvy expresses an opinion about how to handle this.

Maybe the "pg" binary could just pass down it's own location as a parameter to the binary it calls, thereby making sure that binary has direct access to both?

Perhaps. Thus far, I've avoided making the binaries dependent on being called from 'pg'. Having them depend on a parameter that 'pg' passes would be a big change.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Dave Page
dpage@pgadmin.org
In reply to: Mark Dilger (#5)
Re: New 'pg' consolidated metacommand patch

On Wed, May 27, 2020 at 3:00 PM Mark Dilger <mark.dilger@enterprisedb.com>
wrote:

On May 26, 2020, at 4:59 PM, David G. Johnston <

david.g.johnston@gmail.com> wrote:

On Tue, May 26, 2020 at 4:19 PM Mark Dilger <

mark.dilger@enterprisedb.com> wrote:

I'd also appreciate +1 and -1 votes on the overall idea, in case this

entire feature, regardless of implementation, is simply something the
community does not want.

-1, at least as part of core. My question would be how much of this is

would be needed if someone were to create an external project that
installed a "pg" command on top of an existing PostgreSQL installation. Or
put differently, how many of the changes to the existing binaries are
required versus nice-to-have?

If the only goal of something like this were to have a frontend that could
execute the various postgres binaries, then I'd say no changes to those
binaries would be needed, and the frontend would not be worth very much.
The value in having the frontend is that it makes it less difficult to
introduce new commands to the postgres suite of commands, as you don't need
to worry about whether another executable by the same name might happen to
already exist somewhere. Even introducing a command named "pg" has already
gotten such a response on this thread. By having the commands installed in
postgres's libexec rather than bin, you can put whatever commands you want
in libexec without worrying about conflicts. That still leaves open the
question of whether existing commands get moved into libexec, and if so, if
they keep the same name. An external project for this would be worthless
in this regard, as the community wouldn't get any benefit when debating the
merits of introducing a new command vs. the potential for conflicts.

The issue you raise can almost certainly be resolved simply by prefixing
pg- or something similar on all the existing binary names.

I think the beauty of having a single CLI executable is that we can
redesign the user interface to make it nice and consistent for all the
different functions it offers, and to cleanup old cruft such as createuser
vs. createrole and pgbench vs. pg_* and so on.

--
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#9Christoph Moench-Tegeder
cmt@burggraben.net
In reply to: Magnus Hagander (#4)
Re: New 'pg' consolidated metacommand patch

## Magnus Hagander (magnus@hagander.net):

Ugh, yeah, please don't do that. Renaming them just to make it "look more
modern" helps nobody, really. Especially if the suggestion is people should
be using the shared-launcher binary anyway.

Quick, let's invent a fancy name like "microcommand" for doing this
like we're used to; then we tell people that's the new "modern" (anybody
cares to write a Medium article for that? (why Medium? it's neither
Rare nor Well Done)). What might make sense for (some) version control
systems and is tempting in languages which have forgotten howto shared
library might not be the best architecture for everything. What has
become of the collection of small dedicated tools?

Regards,
Christoph

--
Spare Space

#10Robert Haas
robertmhaas@gmail.com
In reply to: Magnus Hagander (#4)
Re: New 'pg' consolidated metacommand patch

On Wed, May 27, 2020 at 4:51 AM Magnus Hagander <magnus@hagander.net> wrote:

As mentioned at least once before, the "pg" name is already taken in posix. Granted it has been removed now, but it was removed from posix in 2018, which I think is nowhere near soon enough to "steal. See for example https://en.wikipedia.org/wiki/Pg_(Unix)

The previous discussion of this general topic starts at
/messages/by-id/CA+TgmoZQmDY7nLrQ96nLm-wrnmNPY90qdMvZ6LtJO941GwgLMg@mail.gmail.com
and the discussion of this particular issue starts at
/messages/by-id/15135.1586703479@sss.pgh.pa.us

I think I agree with what Andres said on that thread: rather than
waiting a long time to see what happens, we should grab the name
before somebody else does. As also discussed on that thread, perhaps
we should have the official name of the binary be 'pgsql' with 'pg' as
a symlink that some packagers might choose to omit.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#11Robert Haas
robertmhaas@gmail.com
In reply to: Magnus Hagander (#4)
Re: New 'pg' consolidated metacommand patch

On Wed, May 27, 2020 at 4:51 AM Magnus Hagander <magnus@hagander.net> wrote:

For that reason, I did not change the names of the executables, merely their location. During conversations with Robert off-list, we discussed renaming the executables to things like 'pg-ctl' (hyphen rather than underscore), mostly because that's the more modern way of doing it and follows what 'git' does. To avoid breaking scripts that execute these commands by the old name, this patch doesn't go that far. It also leaves the usage() functions alone such that when they report their own progname in the usage text, they do so under the old name. This would need to change at some point, but I'm unclear on whether that would be for v14 or if it would be delayed.

Ugh, yeah, please don't do that. Renaming them just to make it "look more modern" helps nobody, really. Especially if the suggestion is people should be using the shared-launcher binary anyway.

The way things like 'git' work is that 'git thunk' just looks in a
designated directory for an executable called git-thunk, and invokes
it if it's found. If you want to invent your own git subcommand, you
can. I guess 'git help' wouldn't know to list it, but you can still
get the metacommand to execute it. That only works if you use a
standard naming, though. If the meta-executable has to hard-code the
names of all the individual executables that it calls, then you can't
really make that work.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Isaac Morland
isaac.morland@gmail.com
In reply to: Robert Haas (#11)
Re: New 'pg' consolidated metacommand patch

On Wed, 27 May 2020 at 12:35, Robert Haas <robertmhaas@gmail.com> wrote:

Ugh, yeah, please don't do that. Renaming them just to make it "look more
modern" helps nobody, really. Especially if the suggestion is people should
be using the shared-launcher binary anyway.

The way things like 'git' work is that 'git thunk' just looks in a
designated directory for an executable called git-thunk, and invokes
it if it's found. If you want to invent your own git subcommand, you
can. I guess 'git help' wouldn't know to list it, but you can still
get the metacommand to execute it. That only works if you use a
standard naming, though. If the meta-executable has to hard-code the
names of all the individual executables that it calls, then you can't
really make that work.

You could make the legacy names symlinks to the new systematic names.

#13Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Mark Dilger (#1)
Re: New 'pg' consolidated metacommand patch

On 2020-05-27 01:19, Mark Dilger wrote:

Attached is a patch for a `pg' command that consolidates various PostgreSQL functionality into a single command, along the lines of how `git' commands are run from a single 'git' executable. In other words,

`pg upgrade` # instead of `pg_upgrade`
`pg resetwal` # instead of `pg_resetwal`

This has been discussed before on the -hackers list, but I don't recall seeing a patch. I'm submitting this patch mostly as a way of moving the conversation along, fully expecting the community to want some (or all) of what I wrote to be changed.

I'd also appreciate +1 and -1 votes on the overall idea, in case this entire feature, regardless of implementation, is simply something the community does not want.

I'm not excited about this.

First, consider that git has over 170 subcommands. PostgreSQL currently
has 36, and we're probably not going to add dozens more any time soon.
So the issue is not of the same scope. It also seems to me that the way
git is organized this becomes a self-perpetuating system: They are
adding subcommands all the time without much care where you might in
other situations think harder about combining them and keeping the
surface area smaller. For example, we wouldn't really need separate
commands clusterdb, reindexdb, vacuumdb if we had better support in psql
for "run this command in each database [in parallel]".

git (and svn etc. before it) also has a much more consistent operating
model that is sensible to reflect in the command structure. They all
more or less operate on a git repository, in apparently 170 different
ways. The 36 PostgreSQL commands don't all work in the same way. Now
if someone were to propose a way to combine server tools, perhaps like
pginstancetool {init|controldata|resetwal|checksum}, and perhaps also in
a way that actually saves code duplication and inconsistency, that would
be something to consider. Or maybe a client-side tool that does
pgclienttool {create user|drop user|create database|...} -- but that
pretty much already exists by the name of psql. But just renaming
everything that's shipped with PostgreSQL to one common bucket without
regard to how it actually works and what role it plays would be
unnecessarily confusing.

Also consider some practical concerns with the command structure you
describe: Tab completion of commands wouldn't work anymore, unless you
supply custom tab completion setups. The direct association between a
command and its man page would be broken. Shell scripting becomes more
challenging: Instead of writing common things like "if which
pg_waldump; then" you'd need some custom code, to be determined. These
are all solvable, but just a sum of slight annoyances, for no real benefit.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#14Isaac Morland
isaac.morland@gmail.com
In reply to: Peter Eisentraut (#13)
Re: New 'pg' consolidated metacommand patch

On Wed, 27 May 2020 at 16:00, Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

Also consider some practical concerns with the command structure you
describe: Tab completion of commands wouldn't work anymore, unless you
supply custom tab completion setups. The direct association between a
command and its man page would be broken. Shell scripting becomes more
challenging: Instead of writing common things like "if which
pg_waldump; then" you'd need some custom code, to be determined. These
are all solvable, but just a sum of slight annoyances, for no real benefit.

I don’t think the man page concern is justified. We could have a “help”
subcommand, just like git; “git help add” is (to a casual observer;
probably not precisely) the same as “man git-add”.

#15Christopher Browne
cbbrowne@gmail.com
In reply to: Isaac Morland (#14)
Re: New 'pg' consolidated metacommand patch

On Wed, 27 May 2020 at 16:49, Isaac Morland <isaac.morland@gmail.com> wrote:

On Wed, 27 May 2020 at 16:00, Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

Also consider some practical concerns with the command structure you
describe: Tab completion of commands wouldn't work anymore, unless you
supply custom tab completion setups. The direct association between a
command and its man page would be broken. Shell scripting becomes more
challenging: Instead of writing common things like "if which
pg_waldump; then" you'd need some custom code, to be determined. These
are all solvable, but just a sum of slight annoyances, for no real
benefit.

I don’t think the man page concern is justified. We could have a “help”
subcommand, just like git; “git help add” is (to a casual observer;
probably not precisely) the same as “man git-add”.

There's some very small gulf in between the concerns...

- On the one hand, git (and systems with similar "keyword" subsystems) have
arrived at reasonable solutions to cope with various of the systematic
issues, so that these shouldn't be considered to be gigantic insurmountable
barriers.

Indeed, some of these tools present systematic solutions to additional
matters. I was very pleased when I found that some of the Kubernetes tools
I was working with included subcommands to configure my shell to know how
to do command completion. Seems like a fine thing to me to have that
become systematically *easier*, and I think that would be a good new
subcommand... "pg completion bash" and "pg completion zsh" would be mighty
fine things.

- On the other hand, mapping old commands that are names of programs onto
"pg subcommands" is some additional effort, and we haven't yet started
bikeshedding on the favoured names :-)

I have lately noticed some interesting looking apps wandering about that
briefly attracted my attention, but, which, due to being painfully
different from the existing commands and tools that I have already learned,
and have "muscle memory" for, am loath to leave. I'll throw out 4
examples, 3 of them personal:
a) nnn is a terminal-based file manager. It has some nifty features
surrounding the concept that you can set up custom file filters to look for
sorts of files that you find interesting, and then offers customizable UI
for running favorite actions against them. I'm 25 years into using Emacs
Dired mode; as neat as nnn seems, it's not enough of an improvement to be
worth the pain in the neck of relearning stuff.
b) 3mux is a redo of tmux (which was a redo of GNU Screen), and has key
mappings that make it way easier for a new user to learn. I'm 20-ish years
into Screen/Tmux; I wasn't looking for it to be easier to learn, because I
did that quite a while ago.
c) Kakoune is a vi-like editor which rotates from vi's "verb/object"
approach to commands to a "object/verb" approach, for apparent more
efficiency. I think I already mentioned that my "muscle memory" is biased
by Emacs features... I'm not adding a "rotated-vi-like" thing into my mix
:-(
d) systemd is a Controversial System; the folk that seem particularly irate
about it seem to be "Old Bearded Sysadmins" that hate the idea of redoing
their understandings of how Unix systems initialize. Personally, my
feelings are ambivalent; I'm using it where I find some use, and have not
been displeased with my results. And since modern systems now have USB and
network devices added and dropped on a whim, there's a critical need for
something newer with more dynamic responses than old SysV Init. But I
certainly "get" that some aren't so happy with it, and I'm not thrilled at
the ongoing scope creep that never seems to end.

There is merit to having a new, harmonious set of "pg commands." But it's
eminently easy to get into trouble (and get people mad) by changing things
that have been working fine for many years. Half the battle (against the
"getting people mad" part) is making sure that it's clear that people were
listened to. Listening to the community is one of the important things to
do :-).
--
When confronted by a difficult problem, solve it by reducing it to the
question, "How would the Lone Ranger handle this?"

#16Mark Dilger
mark.dilger@enterprisedb.com
In reply to: Christopher Browne (#15)
Re: New 'pg' consolidated metacommand patch

On May 27, 2020, at 2:42 PM, Christopher Browne <cbbrowne@gmail.com> wrote:

There is merit to having a new, harmonious set of "pg commands." But it's eminently easy to get into trouble (and get people mad) by changing things that have been working fine for many years. Half the battle (against the "getting people mad" part) is making sure that it's clear that people were listened to. Listening to the community is one of the important things to do :-).

I totally agree.

There are options for keeping the existing tools and not modifying them. If the "pg" command (or "pgsql" command, if we use that naming) knows, for example, how to execute pg_ctl, that's no harm to people who prefer to just run pg_ctl directly. It only becomes a problem when this patch, or one like it, decides that "pg_ctl" needs to work differently, have a different set of command line options, etc. The only thing I changed about pg_ctl and friends in the v1 patch is that they moved from BINDIR to LIBEXECDIR, and internally they were updated to be able to still work despite the move. That change was partly designed to spark conversation. If people prefer they get moved back into BINDIR, fine by me. If people instead prefer that the patch include links between the old BINDIR location and the new LIBEXECDIR location, that's also fine by me. The "pg" command doesn't really care either. I'm intentionally not calling the shots here. I'm asking the community members, many of whom expressed an interest in something along the lines of this patch. I'm happy to do the grunt work of the patch to meet the community needs.

Dave Page expressed an interest upthread in standardizing the interfaces of the various commands. He didn't say this, but I assume he is thinking about things like -d meaning --debug in initdb but meaning --dbname=CONNSTR in pg_basebackup. We could break backwards compatibility by changing one or both of those commands to interpret those options in some new standardized way. Or, we could preserve backwards compatibililty by having "pg" take --dbname and --debug options and pass them to the subcommand according to the grandfathered rules of the subcommand. I tend towards preserving compability, but maybe somebody on this list wants to argue for the other side? For new commands introduced after this patch gets committed (assuming it does), options could be passed from "pg" through to the subcommand unmolested. That supports Robert's idea that people could install new subcommands from contrib modules without the "pg" command needing to know anything about them. This, too, is still open to conversation and debate.

I'd like to hear from more community members on this. I'm listening.


Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#17Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Christopher Browne (#15)
Re: New 'pg' consolidated metacommand patch

On 2020-05-27 23:42, Christopher Browne wrote:

d) systemd is a Controversial System; the folk that seem particularly
irate about it seem to be "Old Bearded Sysadmins" that hate the idea of
redoing their understandings of how Unix systems initialize. Personally,
my feelings are ambivalent; I'm using it where I find some use, and have
not been displeased with my results.  And since modern systems now have
USB and network devices added and dropped on a whim, there's a critical
need for something newer with more dynamic responses than old SysV
Init.  But I certainly "get" that some aren't so happy with it, and I'm
not thrilled at the ongoing scope creep that never seems to end.

It is worth noting that systemd did not go for a one-binary-for-all
approach. It has different binaries for different parts of the
functionality. systemctl for controlling services, journalctl for
controlling the journal, etc. Just as a data point to show that there
is no single "new" way to do things.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#18Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#13)
Re: New 'pg' consolidated metacommand patch

On Wed, May 27, 2020 at 4:00 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

First, consider that git has over 170 subcommands. PostgreSQL currently
has 36, and we're probably not going to add dozens more any time soon.
So the issue is not of the same scope. It also seems to me that the way
git is organized this becomes a self-perpetuating system: They are
adding subcommands all the time without much care where you might in
other situations think harder about combining them and keeping the
surface area smaller. For example, we wouldn't really need separate
commands clusterdb, reindexdb, vacuumdb if we had better support in psql
for "run this command in each database [in parallel]".

I see your point here, but I think it's clear that some people are
already concerned about the proliferation of binaries on namespace
grounds. For example, it was proposed that pg_verifybackup should be
part of pg_basebackup, and then later and by someone else that it
should be part of pg_checksums. It's quite different from either of
those things and I'm pretty confident it shouldn't be merged with
either one, but there was certainly pressure in that direction. So
apparently for some people 36 is already too many. It's not clear to
me why it should be a problem to have a lot of commands, as long as
they all start with "pg_", but if it is then we should do something
about it rather than waiting until we have more of them.

git (and svn etc. before it) also has a much more consistent operating
model that is sensible to reflect in the command structure. They all
more or less operate on a git repository, in apparently 170 different
ways. The 36 PostgreSQL commands don't all work in the same way. Now
if someone were to propose a way to combine server tools, perhaps like
pginstancetool {init|controldata|resetwal|checksum}, and perhaps also in
a way that actually saves code duplication and inconsistency, that would
be something to consider. Or maybe a client-side tool that does
pgclienttool {create user|drop user|create database|...} -- but that
pretty much already exists by the name of psql. But just renaming
everything that's shipped with PostgreSQL to one common bucket without
regard to how it actually works and what role it plays would be
unnecessarily confusing.

This doesn't strike me as a very practical proposal because
"pginstancetool checksums" is really stinking long compared to
"pg_checksums", where as "pg checksums" is no different, or one
keystroke better if you assume that the underscore requires pressing
shift.

Also consider some practical concerns with the command structure you
describe: Tab completion of commands wouldn't work anymore, unless you
supply custom tab completion setups. The direct association between a
command and its man page would be broken. Shell scripting becomes more
challenging: Instead of writing common things like "if which
pg_waldump; then" you'd need some custom code, to be determined. These
are all solvable, but just a sum of slight annoyances, for no real benefit.

There are some potential benefits, I think, such as:

1. It seems to be the emerging standard for command line interfaces.
There's not only the 'git' example but also things like 'aws', which
is perhaps more similar to the case proposed here in that there are a
bunch of subcommands that do quite different sorts of things. I think
a lot of developers are now quite familiar with the idea of a main
command with a bunch of subcommands, and they expect to be able to
type 'git help' or 'aws help' or 'pg help' to get a list of commands,
and then 'pg help createdb' for help with that. If you don't know what
pg commands exist today, how do you discover them? You're right that
not everyone is going this way but it seems to be pretty common
(kubectl, yum, brew, npm, heroku, ...).

2. It lowers the barrier to adding more commands. For example, as
Chris Browne says, we could have a 'pg completion' command to emit
completion information for various shells. True, that would be more
necessary with this proposal. But I bet there's stuff that could be
done even today -- I think most modern shells have pretty powerful
completion facilities. Someone could propose it, but what are the
chances that a patch adding a pg_completion binary would be accepted?
I feel like the argument that this is too narrow to justify the
namespace pollution is almost inevitable.

3. It might help us achieve some better consistency between commands.
Right now we have a lot of warts in the way things are named, like
pgbench vs. pg_ctl vs. createdb, and also pg_basebackup vs.
pg_test_fsync (why not pg_base_backup or pg_testfsync?). Standardizing
on something like this would probably help us be more consistent
there. Over time, we might be able to also clean other things up.
Maybe we could get all of our client-side utilities to share a common
config file, and have a 'pg config' utility to configure it. Maybe we
could have common options that are shared by all commands as 'git'
does. These things aren't impossible without unifying the interface,
but unifying the interface does help to make it clearer why the other
things should also be unified.

Now maybe it's just not worth it. I'm pretty sure that if we made this
change I would spend some time cursing this because my fingers would
type commands that don't work any more, and that could be annoying,
and I suspect a lot of other people might feel similarly. I don't
think this is something we HAVE to do, but I am a little worried that
we're otherwise locked into a system that isn't particularly scalable
to more commands and doesn't really have any sort of unifying design
either.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company