Diagnose incompatible OpenLDAP versions during build and test.
authorNoah Misch <noah@leadboat.com>
Tue, 22 Jul 2014 15:01:03 +0000 (11:01 -0400)
committerNoah Misch <noah@leadboat.com>
Tue, 22 Jul 2014 15:01:03 +0000 (11:01 -0400)
With OpenLDAP versions 2.4.24 through 2.4.31, inclusive, PostgreSQL
backends can crash at exit.  Raise a warning during "configure" based on
the compile-time OpenLDAP version number, and test the crash scenario in
the dblink test suite.  Back-patch to 9.0 (all supported versions).

configure
configure.in
contrib/dblink/Makefile
contrib/dblink/expected/.gitignore [new file with mode: 0644]
contrib/dblink/expected/dblink.out
contrib/dblink/input/paths.source [new file with mode: 0644]
contrib/dblink/output/paths.source [new file with mode: 0644]
contrib/dblink/pg_service.conf [new file with mode: 0644]
contrib/dblink/sql/.gitignore [new file with mode: 0644]
contrib/dblink/sql/dblink.sql
src/test/regress/regress.c

index 7a97bdc2ce094f63f5115895b1edab2d4bc2a8f7..beb65ebffaa2b7090e105d8a483e1d1b550cadf8 100755 (executable)
--- a/configure
+++ b/configure
@@ -9475,6 +9475,17 @@ fi
 
 fi
 
+# PGAC_LDAP_SAFE
+# --------------
+# PostgreSQL sometimes loads libldap_r and plain libldap into the same
+# process.  Check for OpenLDAP versions known not to tolerate doing so; assume
+# non-OpenLDAP implementations are safe.  The dblink test suite exercises the
+# hazardous interaction directly.
+
+
+
+
+
 if test "$with_ldap" = yes ; then
   if test "$PORTNAME" != "win32"; then
      for ac_header in ldap.h
@@ -9491,6 +9502,47 @@ fi
 
 done
 
+     { $as_echo "$as_me:${as_lineno-$LINENO}: checking for compatible LDAP implementation" >&5
+$as_echo_n "checking for compatible LDAP implementation... " >&6; }
+if ${pgac_cv_ldap_safe+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ldap.h>
+#if !defined(LDAP_VENDOR_VERSION) || \
+     (defined(LDAP_API_FEATURE_X_OPENLDAP) && \
+      LDAP_VENDOR_VERSION >= 20424 && LDAP_VENDOR_VERSION <= 20431)
+choke me
+#endif
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  pgac_cv_ldap_safe=yes
+else
+  pgac_cv_ldap_safe=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_ldap_safe" >&5
+$as_echo "$pgac_cv_ldap_safe" >&6; }
+
+if test "$pgac_cv_ldap_safe" != yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING:
+*** With OpenLDAP versions 2.4.24 through 2.4.31, inclusive, each backend
+*** process that loads libpq (via WAL receiver, dblink, or postgres_fdw) and
+*** also uses LDAP will crash on exit." >&5
+$as_echo "$as_me: WARNING:
+*** With OpenLDAP versions 2.4.24 through 2.4.31, inclusive, each backend
+*** process that loads libpq (via WAL receiver, dblink, or postgres_fdw) and
+*** also uses LDAP will crash on exit." >&2;}
+fi
   else
      for ac_header in winldap.h
 do :
index 0d7cb5a77c6bf4d2eadabfeba84393f6c13a1ae1..e22f8d8ad763a17e6338de14f506daba521a782d 100644 (file)
@@ -1097,10 +1097,39 @@ if test "$with_libxslt" = yes ; then
   AC_CHECK_HEADER(libxslt/xslt.h, [], [AC_MSG_ERROR([header file <libxslt/xslt.h> is required for XSLT support])])
 fi
 
+# PGAC_LDAP_SAFE
+# --------------
+# PostgreSQL sometimes loads libldap_r and plain libldap into the same
+# process.  Check for OpenLDAP versions known not to tolerate doing so; assume
+# non-OpenLDAP implementations are safe.  The dblink test suite exercises the
+# hazardous interaction directly.
+
+AC_DEFUN([PGAC_LDAP_SAFE],
+[AC_CACHE_CHECK([for compatible LDAP implementation], [pgac_cv_ldap_safe],
+[AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[#include <ldap.h>
+#if !defined(LDAP_VENDOR_VERSION) || \
+     (defined(LDAP_API_FEATURE_X_OPENLDAP) && \
+      LDAP_VENDOR_VERSION >= 20424 && LDAP_VENDOR_VERSION <= 20431)
+choke me
+#endif], [])],
+[pgac_cv_ldap_safe=yes],
+[pgac_cv_ldap_safe=no])])
+
+if test "$pgac_cv_ldap_safe" != yes; then
+  AC_MSG_WARN([
+*** With OpenLDAP versions 2.4.24 through 2.4.31, inclusive, each backend
+*** process that loads libpq (via WAL receiver, dblink, or postgres_fdw) and
+*** also uses LDAP will crash on exit.])
+fi])
+
+
+
 if test "$with_ldap" = yes ; then
   if test "$PORTNAME" != "win32"; then
      AC_CHECK_HEADERS(ldap.h, [],
                       [AC_MSG_ERROR([header file <ldap.h> is required for LDAP])])
+     PGAC_LDAP_SAFE
   else
      AC_CHECK_HEADERS(winldap.h, [],
                       [AC_MSG_ERROR([header file <winldap.h> is required for LDAP])],
index 9242ed74dc82d28d1610e9e3a26f06cd2b1ce195..e5d0cd6bf00a426ebcc2d136a1a7686901fa8cae 100644 (file)
@@ -10,7 +10,9 @@ EXTENSION = dblink
 DATA = dblink--1.1.sql dblink--1.0--1.1.sql dblink--unpackaged--1.0.sql
 PGFILEDESC = "dblink - connect to other PostgreSQL databases"
 
-REGRESS = dblink
+REGRESS = paths dblink
+REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
+EXTRA_CLEAN = sql/paths.sql expected/paths.out
 
 # the db name is hard-coded in the tests
 override USE_MODULE_DB =
diff --git a/contrib/dblink/expected/.gitignore b/contrib/dblink/expected/.gitignore
new file mode 100644 (file)
index 0000000..d9c7942
--- /dev/null
@@ -0,0 +1 @@
+/paths.out
index 36fdf733fe2bc41cd96eb00020fc8e7711a3f82f..eee31a249f3afbf3ff8d87841cd16ffb7cce60d9 100644 (file)
@@ -103,6 +103,33 @@ SELECT *
 FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[])
 WHERE t.a > 7;
 ERROR:  connection not available
+-- The first-level connection's backend will crash on exit given OpenLDAP
+-- [2.4.24, 2.4.31].  We won't see evidence of any crash until the victim
+-- process terminates and the postmaster responds.  If process termination
+-- entails writing a core dump, that can take awhile.  Wait for the process to
+-- vanish.  At that point, the postmaster has called waitpid() on the crashed
+-- process, and it will accept no new connections until it has reinitialized
+-- the cluster.  (We can't exploit pg_stat_activity, because the crash happens
+-- after the backend updates shared memory to reflect its impending exit.)
+DO $pl$
+DECLARE
+       detail text;
+BEGIN
+       PERFORM wait_pid(crash_pid)
+       FROM dblink('dbname=contrib_regression', $$
+               SELECT pg_backend_pid() FROM dblink(
+                       'service=test_ldap dbname=contrib_regression',
+                       -- This string concatenation is a hack to shoehorn a
+                       -- set_pgservicefile call into the SQL statement.
+                       'SELECT 1' || set_pgservicefile('pg_service.conf')
+               ) t(c int)
+       $$) AS t(crash_pid int);
+EXCEPTION WHEN OTHERS THEN
+       GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
+       -- Expected error in a non-LDAP build.
+       IF NOT detail LIKE 'syntax error in service file%' THEN RAISE; END IF;
+END
+$pl$;
 -- create a persistent connection
 SELECT dblink_connect('dbname=contrib_regression');
  dblink_connect 
diff --git a/contrib/dblink/input/paths.source b/contrib/dblink/input/paths.source
new file mode 100644 (file)
index 0000000..aab3a3b
--- /dev/null
@@ -0,0 +1,14 @@
+-- Initialization that requires path substitution.
+
+CREATE FUNCTION putenv(text)
+   RETURNS void
+   AS '@libdir@/regress@DLSUFFIX@', 'regress_putenv'
+   LANGUAGE C STRICT;
+
+CREATE FUNCTION wait_pid(int)
+   RETURNS void
+   AS '@libdir@/regress@DLSUFFIX@'
+   LANGUAGE C STRICT;
+
+CREATE FUNCTION set_pgservicefile(text) RETURNS void LANGUAGE SQL
+    AS $$SELECT putenv('PGSERVICEFILE=@abs_srcdir@/' || $1)$$;
diff --git a/contrib/dblink/output/paths.source b/contrib/dblink/output/paths.source
new file mode 100644 (file)
index 0000000..e1097f0
--- /dev/null
@@ -0,0 +1,11 @@
+-- Initialization that requires path substitution.
+CREATE FUNCTION putenv(text)
+   RETURNS void
+   AS '@libdir@/regress@DLSUFFIX@', 'regress_putenv'
+   LANGUAGE C STRICT;
+CREATE FUNCTION wait_pid(int)
+   RETURNS void
+   AS '@libdir@/regress@DLSUFFIX@'
+   LANGUAGE C STRICT;
+CREATE FUNCTION set_pgservicefile(text) RETURNS void LANGUAGE SQL
+    AS $$SELECT putenv('PGSERVICEFILE=@abs_srcdir@/' || $1)$$;
diff --git a/contrib/dblink/pg_service.conf b/contrib/dblink/pg_service.conf
new file mode 100644 (file)
index 0000000..92201f0
--- /dev/null
@@ -0,0 +1,7 @@
+# pg_service.conf for minimally exercising libpq use of LDAP.
+
+# Having failed to reach an LDAP server, libpq essentially ignores the
+# "service=test_ldap" in its connection string.  Contact the "discard"
+# service; the test works whether or not it answers.
+[test_ldap]
+ldap://127.0.0.1:9/base?attribute?one?filter
diff --git a/contrib/dblink/sql/.gitignore b/contrib/dblink/sql/.gitignore
new file mode 100644 (file)
index 0000000..d175078
--- /dev/null
@@ -0,0 +1 @@
+/paths.sql
index 30396ed9844113c38529858c5f72c5db0ea1e2d5..500c684a91c3874f5376d97ec0e4253c9af2c75a 100644 (file)
@@ -65,6 +65,34 @@ SELECT *
 FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[])
 WHERE t.a > 7;
 
+-- The first-level connection's backend will crash on exit given OpenLDAP
+-- [2.4.24, 2.4.31].  We won't see evidence of any crash until the victim
+-- process terminates and the postmaster responds.  If process termination
+-- entails writing a core dump, that can take awhile.  Wait for the process to
+-- vanish.  At that point, the postmaster has called waitpid() on the crashed
+-- process, and it will accept no new connections until it has reinitialized
+-- the cluster.  (We can't exploit pg_stat_activity, because the crash happens
+-- after the backend updates shared memory to reflect its impending exit.)
+DO $pl$
+DECLARE
+       detail text;
+BEGIN
+       PERFORM wait_pid(crash_pid)
+       FROM dblink('dbname=contrib_regression', $$
+               SELECT pg_backend_pid() FROM dblink(
+                       'service=test_ldap dbname=contrib_regression',
+                       -- This string concatenation is a hack to shoehorn a
+                       -- set_pgservicefile call into the SQL statement.
+                       'SELECT 1' || set_pgservicefile('pg_service.conf')
+               ) t(c int)
+       $$) AS t(crash_pid int);
+EXCEPTION WHEN OTHERS THEN
+       GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL;
+       -- Expected error in a non-LDAP build.
+       IF NOT detail LIKE 'syntax error in service file%' THEN RAISE; END IF;
+END
+$pl$;
+
 -- create a persistent connection
 SELECT dblink_connect('dbname=contrib_regression');
 
index c1461092fb386e3a97dd3cc971a90e5ea86a4242..09e027c1e5b96dc5c5744ab259a0efa71741ac50 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <float.h>
 #include <math.h>
+#include <signal.h>
 
 #include "access/htup_details.h"
 #include "access/transam.h"
@@ -16,6 +17,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/geo_decls.h"
 #include "utils/rel.h"
@@ -822,3 +824,44 @@ make_tuple_indirect(PG_FUNCTION_ARGS)
         */
        PG_RETURN_POINTER(newtup->t_data);
 }
+
+PG_FUNCTION_INFO_V1(regress_putenv);
+
+Datum
+regress_putenv(PG_FUNCTION_ARGS)
+{
+       MemoryContext oldcontext;
+       char       *envbuf;
+
+       if (!superuser())
+               elog(ERROR, "must be superuser to change environment variables");
+
+       oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+       envbuf = text_to_cstring((text *) PG_GETARG_POINTER(0));
+       MemoryContextSwitchTo(oldcontext);
+
+       if (putenv(envbuf) != 0)
+               elog(ERROR, "could not set environment variable: %m");
+
+       PG_RETURN_VOID();
+}
+
+/* Sleep until no process has a given PID. */
+PG_FUNCTION_INFO_V1(wait_pid);
+
+Datum
+wait_pid(PG_FUNCTION_ARGS)
+{
+       int                     pid = PG_GETARG_INT32(0);
+
+       if (!superuser())
+               elog(ERROR, "must be superuser to check PID liveness");
+
+       while (kill(pid, 0) == 0)
+               pg_usleep(50000);
+
+       if (errno != ESRCH)
+               elog(ERROR, "could not check PID %d liveness: %m", pid);
+
+       PG_RETURN_VOID();
+}