From 2aee6dbe34727c04741cd7ea1c91bfee8c552a20 Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Wed, 27 Nov 2024 09:19:03 -0600 Subject: [PATCH v8 1/1] fix database name truncation --- src/backend/tcop/backend_startup.c | 35 +++++++++----- src/backend/tcop/postgres.c | 17 ++++++- src/backend/utils/init/postinit.c | 76 +++++++++++++++++++++++++++--- src/include/tcop/backend_startup.h | 1 + 4 files changed, 108 insertions(+), 21 deletions(-) diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c index 2a96c81f92..096c798df5 100644 --- a/src/backend/tcop/backend_startup.c +++ b/src/backend/tcop/backend_startup.c @@ -128,7 +128,7 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac) Port *port; char remote_host[NI_MAXHOST]; char remote_port[NI_MAXSERV]; - StringInfoData ps_data; + char *ps_data; MemoryContext oldcontext; /* Tell fd.c about the long-lived FD associated with the client_sock */ @@ -351,18 +351,9 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac) * Now that we have the user and database name, we can set the process * title for ps. It's good to do this as early as possible in startup. */ - initStringInfo(&ps_data); - if (am_walsender) - appendStringInfo(&ps_data, "%s ", GetBackendTypeDesc(B_WAL_SENDER)); - appendStringInfo(&ps_data, "%s ", port->user_name); - if (port->database_name[0] != '\0') - appendStringInfo(&ps_data, "%s ", port->database_name); - appendStringInfoString(&ps_data, port->remote_host); - if (port->remote_port[0] != '\0') - appendStringInfo(&ps_data, "(%s)", port->remote_port); - - init_ps_display(ps_data.data); - pfree(ps_data.data); + ps_data = get_ps_data(port); + init_ps_display(ps_data); + pfree(ps_data); set_ps_display("initializing"); } @@ -915,3 +906,21 @@ StartupPacketTimeoutHandler(void) { _exit(1); } + +char * +get_ps_data(Port *port) +{ + StringInfoData ps_data; + + initStringInfo(&ps_data); + if (am_walsender) + appendStringInfo(&ps_data, "%s ", GetBackendTypeDesc(B_WAL_SENDER)); + appendStringInfo(&ps_data, "%s ", port->user_name); + if (port->database_name[0] != '\0') + appendStringInfo(&ps_data, "%s ", port->database_name); + appendStringInfoString(&ps_data, port->remote_host); + if (port->remote_port[0] != '\0') + appendStringInfo(&ps_data, "(%s)", port->remote_port); + + return ps_data.data; +} diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index bd42476717..24b9bc0cc0 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -66,6 +66,7 @@ #include "storage/proc.h" #include "storage/procsignal.h" #include "storage/sinval.h" +#include "tcop/backend_startup.h" #include "tcop/fastpath.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -4264,6 +4265,7 @@ void PostgresMain(const char *dbname, const char *username) { sigjmp_buf local_sigjmp_buf; + char out_dbname[NAMEDATALEN]; /* these must be volatile to ensure state is preserved across longjmp: */ volatile bool send_ready_for_query = true; @@ -4365,7 +4367,20 @@ PostgresMain(const char *dbname, const char *username) InitPostgres(dbname, InvalidOid, /* database to connect to */ username, InvalidOid, /* role to connect as */ (!am_walsender) ? INIT_PG_LOAD_SESSION_LIBS : 0, - NULL); /* no out_dbname */ + out_dbname); + + if (MyProcPort && unlikely(strcmp(dbname, out_dbname) != 0)) + { + char *ps_data; + + strcpy(MyProcPort->database_name, out_dbname); + + ps_data = get_ps_data(MyProcPort); + init_ps_display(ps_data); + pfree(ps_data); + + set_ps_display("startup"); + } /* * If the PostmasterContext is still around, recycle the space; we don't diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 5b657a3f13..017a6aa7cf 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -68,6 +68,7 @@ #include "utils/timeout.h" static HeapTuple GetDatabaseTuple(const char *dbname); +static HeapTuple GetDatabaseTupleInternal(Relation relation, const char *dbname); static HeapTuple GetDatabaseTupleByOid(Oid dboid); static void PerformAuthentication(Port *port); static void CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connections); @@ -97,12 +98,72 @@ static void process_settings(Oid databaseid, Oid roleid); * descriptors for both pg_database and its indexes from the shared relcache * cache file, and so we can do an indexscan. criticalSharedRelcachesBuilt * tells whether we got the cached descriptors. + * + * This function also deals with the trickier-than-it-sounds issue of + * truncating an overlength incoming database name. We do not know which + * encoding the name is in, so we can't apply pg_encoding_mbcliplen() as + * CREATE DATABASE would have done. We use the heuristic of truncating one + * byte at a time until we find a match in pg_database, relying on the + * assumption that the name as stored in pg_database is valid according to + * some server-side encoding. This lets us constrain the search using the + * fact that all bytes of a multibyte character have their high bit set. */ static HeapTuple GetDatabaseTuple(const char *dbname) { - HeapTuple tuple; + HeapTuple tuple = NULL; Relation relation; + char tname[NAMEDATALEN]; + + relation = table_open(DatabaseRelationId, AccessShareLock); + + for (int i = NAMEDATALEN - 1; + i >= NAMEDATALEN - MAX_MULTIBYTE_CHAR_LEN; + i--) + { + strncpy(tname, dbname, sizeof(tname)); + tname[i] = '\0'; + + /* + * Take the first match. It's theoretically possible that we could + * find more than one match if we continued to try shorter truncations + * (which is what makes this a heuristic). This is unlikely though. + * We considered failing if there are multiple candidate matches, but + * that cure seems worse than the disease: it might reject cases that + * worked fine before v17. + */ + tuple = GetDatabaseTupleInternal(relation, tname); + if (HeapTupleIsValid(tuple)) + break; + + /* + * On the first iteration, check if we actually truncated anything. If + * not, then we don't need to bother with the successive-truncation + * heuristic. + */ + if (i == NAMEDATALEN - 1 && strlen(dbname) < NAMEDATALEN) + break; + + /* + * If the bytes at the truncation point are ASCII characters, we know + * that they cannot both be part of the same character, so the + * original truncation by CREATE/ALTER DATABASE would have gone no + * further. + */ + if (!IS_HIGHBIT_SET(dbname[i]) || !IS_HIGHBIT_SET(dbname[i - 1])) + break; + } + + table_close(relation, AccessShareLock); + + return tuple; +} + +/* One tuple fetch for GetDatabaseTuple */ +static HeapTuple +GetDatabaseTupleInternal(Relation relation, const char *dbname) +{ + HeapTuple tuple; SysScanDesc scan; ScanKeyData key[1]; @@ -115,11 +176,10 @@ GetDatabaseTuple(const char *dbname) CStringGetDatum(dbname)); /* - * Open pg_database and fetch a tuple. Force heap scan if we haven't yet - * built the critical shared relcache entries (i.e., we're starting up - * without a shared relcache cache file). + * Try to fetch the tuple. Force heap scan if we haven't yet built the + * critical shared relcache entries (i.e., we're starting up without a + * shared relcache cache file). */ - relation = table_open(DatabaseRelationId, AccessShareLock); scan = systable_beginscan(relation, DatabaseNameIndexId, criticalSharedRelcachesBuilt, NULL, @@ -133,7 +193,6 @@ GetDatabaseTuple(const char *dbname) /* all done */ systable_endscan(scan); - table_close(relation, AccessShareLock); return tuple; } @@ -1011,6 +1070,8 @@ InitPostgres(const char *in_dbname, Oid dboid, errmsg("database \"%s\" does not exist", in_dbname))); dbform = (Form_pg_database) GETSTRUCT(tuple); dboid = dbform->oid; + /* Save the real (possibly truncated) database name */ + strlcpy(dbname, NameStr(dbform->datname), sizeof(dbname)); } else if (!OidIsValid(dboid)) { @@ -1066,8 +1127,9 @@ InitPostgres(const char *in_dbname, Oid dboid, if (HeapTupleIsValid(tuple)) datform = (Form_pg_database) GETSTRUCT(tuple); + /* Note comparison here must be against the truncated DB name */ if (!HeapTupleIsValid(tuple) || - (in_dbname && namestrcmp(&datform->datname, in_dbname))) + (in_dbname && namestrcmp(&datform->datname, dbname) != 0)) { if (in_dbname) ereport(FATAL, diff --git a/src/include/tcop/backend_startup.h b/src/include/tcop/backend_startup.h index 993b013afd..6e26c00002 100644 --- a/src/include/tcop/backend_startup.h +++ b/src/include/tcop/backend_startup.h @@ -40,5 +40,6 @@ typedef struct BackendStartupData } BackendStartupData; extern void BackendMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn(); +extern char *get_ps_data(Port *port); #endif /* BACKEND_STARTUP_H */ -- 2.39.5 (Apple Git-154)