diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 5b657a3f13..bfbc41aceb 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; 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,