libpq: Allow developers to reimplement libpq-oauth

For PG19, since we won't have the ability to officially switch out flow
plugins, relax the flow-loading code to not require the internal init
function. Modules that don't have one will be treated as custom user
flows in error messages.

This will let bleeding-edge developers more easily test out the API and
provide feedback for PG20, by telling the runtime linker to find a
different libpq-oauth. It remains undocumented for end users.

Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/CAOYmi%2BmrGg%2Bn_X2MOLgeWcj3v_M00gR8uz_D7mM8z%3DdX1JYVbg%40mail.gmail.com
This commit is contained in:
Jacob Champion 2026-03-31 11:47:26 -07:00
parent 0af4d402cb
commit 09532b4040
2 changed files with 42 additions and 31 deletions

View file

@ -890,8 +890,8 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
"libpq-oauth" DLSUFFIX;
#endif
state->builtin_flow = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
if (!state->builtin_flow)
state->flow_module = dlopen(module_name, RTLD_NOW | RTLD_LOCAL);
if (!state->flow_module)
{
/*
* For end users, this probably isn't an error condition, it just
@ -906,8 +906,16 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
return 0;
}
if ((init = dlsym(state->builtin_flow, "libpq_oauth_init")) == NULL
|| (start_flow = dlsym(state->builtin_flow, "pg_start_oauthbearer")) == NULL)
/*
* Our libpq-oauth.so provides a special initialization function for libpq
* integration. If we don't find this, assume that a custom module is in
* use instead.
*/
init = dlsym(state->flow_module, "libpq_oauth_init");
if (!init)
state->builtin = false; /* adjust our error messages */
if ((start_flow = dlsym(state->flow_module, "pg_start_oauthbearer")) == NULL)
{
/*
* This is more of an error condition than the one above, but the
@ -917,8 +925,8 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
if (oauth_unsafe_debugging_enabled())
fprintf(stderr, "failed dlsym for libpq-oauth: %s\n", dlerror());
dlclose(state->builtin_flow);
state->builtin_flow = NULL;
dlclose(state->flow_module);
state->flow_module = NULL;
request->error = libpq_gettext("could not find entry point for libpq-oauth");
return -1;
@ -929,40 +937,43 @@ use_builtin_flow(PGconn *conn, fe_oauth_state *state, PGoauthBearerRequestV2 *re
* permanently.
*/
/*
* We need to inject necessary function pointers into the module. This
* only needs to be done once -- even if the pointers are constant,
* assigning them while another thread is executing the flows feels like
* tempting fate.
*/
if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
if (init)
{
/* Should not happen... but don't continue if it does. */
Assert(false);
/*
* We need to inject necessary function pointers into the module. This
* only needs to be done once -- even if the pointers are constant,
* assigning them while another thread is executing the flows feels
* like tempting fate.
*/
if ((lockerr = pthread_mutex_lock(&init_mutex)) != 0)
{
/* Should not happen... but don't continue if it does. */
Assert(false);
appendPQExpBuffer(&conn->errorMessage,
"use_builtin_flow: failed to lock mutex (%d)\n",
lockerr);
appendPQExpBuffer(&conn->errorMessage,
"use_builtin_flow: failed to lock mutex (%d)\n",
lockerr);
request->error = ""; /* satisfy report_flow_error() */
return -1;
}
request->error = ""; /* satisfy report_flow_error() */
return -1;
}
if (!initialized)
{
init(
if (!initialized)
{
init(
#ifdef ENABLE_NLS
libpq_gettext
libpq_gettext
#else
NULL
NULL
#endif
);
);
initialized = true;
initialized = true;
}
pthread_mutex_unlock(&init_mutex);
}
pthread_mutex_unlock(&init_mutex);
return (start_flow(conn, request) == 0) ? 1 : -1;
}

View file

@ -36,7 +36,7 @@ typedef struct
bool v1;
bool builtin;
void *builtin_flow;
void *flow_module;
} fe_oauth_state;
extern void pqClearOAuthToken(PGconn *conn);