mirror of https://github.com/postgres/postgres
This is code that runs in the backend process after forking, rather than postmaster. Move it out of postmaster.c for clarity. Reviewed-by: Tristan Partin, Andres Freund Discussion: https://www.postgresql.org/message-id/7a59b073-5b5b-151e-7ed3-8b01ff7ce9ef@iki.fipull/159/head
parent
aafc05de1b
commit
05c3980e7f
@ -0,0 +1,778 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* backend_startup.c |
||||
* Backend startup code |
||||
* |
||||
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* |
||||
* IDENTIFICATION |
||||
* src/backend/tcop/backend_startup.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
#include "postgres.h" |
||||
|
||||
#include <unistd.h> |
||||
|
||||
#include "access/xlog.h" |
||||
#include "common/ip.h" |
||||
#include "common/string.h" |
||||
#include "libpq/libpq.h" |
||||
#include "libpq/libpq-be.h" |
||||
#include "libpq/pqformat.h" |
||||
#include "libpq/pqsignal.h" |
||||
#include "miscadmin.h" |
||||
#include "postmaster/postmaster.h" |
||||
#include "replication/walsender.h" |
||||
#include "storage/fd.h" |
||||
#include "storage/ipc.h" |
||||
#include "storage/proc.h" |
||||
#include "tcop/backend_startup.h" |
||||
#include "tcop/tcopprot.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/memutils.h" |
||||
#include "utils/ps_status.h" |
||||
#include "utils/timeout.h" |
||||
|
||||
static void BackendInitialize(ClientSocket *client_sock, CAC_state cac); |
||||
static int ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done); |
||||
static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options); |
||||
static void process_startup_packet_die(SIGNAL_ARGS); |
||||
static void StartupPacketTimeoutHandler(void); |
||||
|
||||
/*
|
||||
* Entry point for a new backend process. |
||||
* |
||||
* Initialize the connection, read the startup packet, authenticate the |
||||
* client, and start the main processing loop. |
||||
*/ |
||||
void |
||||
BackendMain(char *startup_data, size_t startup_data_len) |
||||
{ |
||||
BackendStartupData *bsdata = (BackendStartupData *) startup_data; |
||||
|
||||
Assert(startup_data_len == sizeof(BackendStartupData)); |
||||
Assert(MyClientSocket != NULL); |
||||
|
||||
#ifdef EXEC_BACKEND |
||||
|
||||
/*
|
||||
* Need to reinitialize the SSL library in the backend, since the context |
||||
* structures contain function pointers and cannot be passed through the |
||||
* parameter file. |
||||
* |
||||
* If for some reason reload fails (maybe the user installed broken key |
||||
* files), soldier on without SSL; that's better than all connections |
||||
* becoming impossible. |
||||
* |
||||
* XXX should we do this in all child processes? For the moment it's |
||||
* enough to do it in backend children. |
||||
*/ |
||||
#ifdef USE_SSL |
||||
if (EnableSSL) |
||||
{ |
||||
if (secure_initialize(false) == 0) |
||||
LoadedSSL = true; |
||||
else |
||||
ereport(LOG, |
||||
(errmsg("SSL configuration could not be loaded in child process"))); |
||||
} |
||||
#endif |
||||
#endif |
||||
|
||||
/* Perform additional initialization and collect startup packet */ |
||||
BackendInitialize(MyClientSocket, bsdata->canAcceptConnections); |
||||
|
||||
/*
|
||||
* Create a per-backend PGPROC struct in shared memory. We must do this |
||||
* before we can use LWLocks or access any shared memory. |
||||
*/ |
||||
InitProcess(); |
||||
|
||||
/*
|
||||
* Make sure we aren't in PostmasterContext anymore. (We can't delete it |
||||
* just yet, though, because InitPostgres will need the HBA data.) |
||||
*/ |
||||
MemoryContextSwitchTo(TopMemoryContext); |
||||
|
||||
PostgresMain(MyProcPort->database_name, MyProcPort->user_name); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* BackendInitialize -- initialize an interactive (postmaster-child) |
||||
* backend process, and collect the client's startup packet. |
||||
* |
||||
* returns: nothing. Will not return at all if there's any failure. |
||||
* |
||||
* Note: this code does not depend on having any access to shared memory. |
||||
* Indeed, our approach to SIGTERM/timeout handling *requires* that |
||||
* shared memory not have been touched yet; see comments within. |
||||
* In the EXEC_BACKEND case, we are physically attached to shared memory |
||||
* but have not yet set up most of our local pointers to shmem structures. |
||||
*/ |
||||
static void |
||||
BackendInitialize(ClientSocket *client_sock, CAC_state cac) |
||||
{ |
||||
int status; |
||||
int ret; |
||||
Port *port; |
||||
char remote_host[NI_MAXHOST]; |
||||
char remote_port[NI_MAXSERV]; |
||||
StringInfoData ps_data; |
||||
MemoryContext oldcontext; |
||||
|
||||
/* Tell fd.c about the long-lived FD associated with the client_sock */ |
||||
ReserveExternalFD(); |
||||
|
||||
/*
|
||||
* PreAuthDelay is a debugging aid for investigating problems in the |
||||
* authentication cycle: it can be set in postgresql.conf to allow time to |
||||
* attach to the newly-forked backend with a debugger. (See also |
||||
* PostAuthDelay, which we allow clients to pass through PGOPTIONS, but it |
||||
* is not honored until after authentication.) |
||||
*/ |
||||
if (PreAuthDelay > 0) |
||||
pg_usleep(PreAuthDelay * 1000000L); |
||||
|
||||
/* This flag will remain set until InitPostgres finishes authentication */ |
||||
ClientAuthInProgress = true; /* limit visibility of log messages */ |
||||
|
||||
/*
|
||||
* Initialize libpq and enable reporting of ereport errors to the client. |
||||
* Must do this now because authentication uses libpq to send messages. |
||||
* |
||||
* The Port structure and all data structures attached to it are allocated |
||||
* in TopMemoryContext, so that they survive into PostgresMain execution. |
||||
* We need not worry about leaking this storage on failure, since we |
||||
* aren't in the postmaster process anymore. |
||||
*/ |
||||
oldcontext = MemoryContextSwitchTo(TopMemoryContext); |
||||
port = MyProcPort = pq_init(client_sock); |
||||
MemoryContextSwitchTo(oldcontext); |
||||
|
||||
whereToSendOutput = DestRemote; /* now safe to ereport to client */ |
||||
|
||||
/* set these to empty in case they are needed before we set them up */ |
||||
port->remote_host = ""; |
||||
port->remote_port = ""; |
||||
|
||||
/*
|
||||
* We arrange to do _exit(1) if we receive SIGTERM or timeout while trying |
||||
* to collect the startup packet; while SIGQUIT results in _exit(2). |
||||
* Otherwise the postmaster cannot shutdown the database FAST or IMMED |
||||
* cleanly if a buggy client fails to send the packet promptly. |
||||
* |
||||
* Exiting with _exit(1) is only possible because we have not yet touched |
||||
* shared memory; therefore no outside-the-process state needs to get |
||||
* cleaned up. |
||||
*/ |
||||
pqsignal(SIGTERM, process_startup_packet_die); |
||||
/* SIGQUIT handler was already set up by InitPostmasterChild */ |
||||
InitializeTimeouts(); /* establishes SIGALRM handler */ |
||||
sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL); |
||||
|
||||
/*
|
||||
* Get the remote host name and port for logging and status display. |
||||
*/ |
||||
remote_host[0] = '\0'; |
||||
remote_port[0] = '\0'; |
||||
if ((ret = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen, |
||||
remote_host, sizeof(remote_host), |
||||
remote_port, sizeof(remote_port), |
||||
(log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) != 0) |
||||
ereport(WARNING, |
||||
(errmsg_internal("pg_getnameinfo_all() failed: %s", |
||||
gai_strerror(ret)))); |
||||
|
||||
/*
|
||||
* Save remote_host and remote_port in port structure (after this, they |
||||
* will appear in log_line_prefix data for log messages). |
||||
*/ |
||||
oldcontext = MemoryContextSwitchTo(TopMemoryContext); |
||||
port->remote_host = pstrdup(remote_host); |
||||
port->remote_port = pstrdup(remote_port); |
||||
|
||||
/* And now we can issue the Log_connections message, if wanted */ |
||||
if (Log_connections) |
||||
{ |
||||
if (remote_port[0]) |
||||
ereport(LOG, |
||||
(errmsg("connection received: host=%s port=%s", |
||||
remote_host, |
||||
remote_port))); |
||||
else |
||||
ereport(LOG, |
||||
(errmsg("connection received: host=%s", |
||||
remote_host))); |
||||
} |
||||
|
||||
/*
|
||||
* If we did a reverse lookup to name, we might as well save the results |
||||
* rather than possibly repeating the lookup during authentication. |
||||
* |
||||
* Note that we don't want to specify NI_NAMEREQD above, because then we'd |
||||
* get nothing useful for a client without an rDNS entry. Therefore, we |
||||
* must check whether we got a numeric IPv4 or IPv6 address, and not save |
||||
* it into remote_hostname if so. (This test is conservative and might |
||||
* sometimes classify a hostname as numeric, but an error in that |
||||
* direction is safe; it only results in a possible extra lookup.) |
||||
*/ |
||||
if (log_hostname && |
||||
ret == 0 && |
||||
strspn(remote_host, "0123456789.") < strlen(remote_host) && |
||||
strspn(remote_host, "0123456789ABCDEFabcdef:") < strlen(remote_host)) |
||||
{ |
||||
port->remote_hostname = pstrdup(remote_host); |
||||
} |
||||
MemoryContextSwitchTo(oldcontext); |
||||
|
||||
/*
|
||||
* Ready to begin client interaction. We will give up and _exit(1) after |
||||
* a time delay, so that a broken client can't hog a connection |
||||
* indefinitely. PreAuthDelay and any DNS interactions above don't count |
||||
* against the time limit. |
||||
* |
||||
* Note: AuthenticationTimeout is applied here while waiting for the |
||||
* startup packet, and then again in InitPostgres for the duration of any |
||||
* authentication operations. So a hostile client could tie up the |
||||
* process for nearly twice AuthenticationTimeout before we kick him off. |
||||
* |
||||
* Note: because PostgresMain will call InitializeTimeouts again, the |
||||
* registration of STARTUP_PACKET_TIMEOUT will be lost. This is okay |
||||
* since we never use it again after this function. |
||||
*/ |
||||
RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler); |
||||
enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000); |
||||
|
||||
/*
|
||||
* Receive the startup packet (which might turn out to be a cancel request |
||||
* packet). |
||||
*/ |
||||
status = ProcessStartupPacket(port, false, false); |
||||
|
||||
/*
|
||||
* If we're going to reject the connection due to database state, say so |
||||
* now instead of wasting cycles on an authentication exchange. (This also |
||||
* allows a pg_ping utility to be written.) |
||||
*/ |
||||
if (status == STATUS_OK) |
||||
{ |
||||
switch (cac) |
||||
{ |
||||
case CAC_STARTUP: |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW), |
||||
errmsg("the database system is starting up"))); |
||||
break; |
||||
case CAC_NOTCONSISTENT: |
||||
if (EnableHotStandby) |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW), |
||||
errmsg("the database system is not yet accepting connections"), |
||||
errdetail("Consistent recovery state has not been yet reached."))); |
||||
else |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW), |
||||
errmsg("the database system is not accepting connections"), |
||||
errdetail("Hot standby mode is disabled."))); |
||||
break; |
||||
case CAC_SHUTDOWN: |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW), |
||||
errmsg("the database system is shutting down"))); |
||||
break; |
||||
case CAC_RECOVERY: |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW), |
||||
errmsg("the database system is in recovery mode"))); |
||||
break; |
||||
case CAC_TOOMANY: |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_TOO_MANY_CONNECTIONS), |
||||
errmsg("sorry, too many clients already"))); |
||||
break; |
||||
case CAC_OK: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* Disable the timeout, and prevent SIGTERM again. |
||||
*/ |
||||
disable_timeout(STARTUP_PACKET_TIMEOUT, false); |
||||
sigprocmask(SIG_SETMASK, &BlockSig, NULL); |
||||
|
||||
/*
|
||||
* As a safety check that nothing in startup has yet performed |
||||
* shared-memory modifications that would need to be undone if we had |
||||
* exited through SIGTERM or timeout above, check that no on_shmem_exit |
||||
* handlers have been registered yet. (This isn't terribly bulletproof, |
||||
* since someone might misuse an on_proc_exit handler for shmem cleanup, |
||||
* but it's a cheap and helpful check. We cannot disallow on_proc_exit |
||||
* handlers unfortunately, since pq_init() already registered one.) |
||||
*/ |
||||
check_on_shmem_exit_lists_are_empty(); |
||||
|
||||
/*
|
||||
* Stop here if it was bad or a cancel packet. ProcessStartupPacket |
||||
* already did any appropriate error reporting. |
||||
*/ |
||||
if (status != STATUS_OK) |
||||
proc_exit(0); |
||||
|
||||
/*
|
||||
* 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); |
||||
|
||||
set_ps_display("initializing"); |
||||
} |
||||
|
||||
/*
|
||||
* Read a client's startup packet and do something according to it. |
||||
* |
||||
* Returns STATUS_OK or STATUS_ERROR, or might call ereport(FATAL) and |
||||
* not return at all. |
||||
* |
||||
* (Note that ereport(FATAL) stuff is sent to the client, so only use it |
||||
* if that's what you want. Return STATUS_ERROR if you don't want to |
||||
* send anything to the client, which would typically be appropriate |
||||
* if we detect a communications failure.) |
||||
* |
||||
* Set ssl_done and/or gss_done when negotiation of an encrypted layer |
||||
* (currently, TLS or GSSAPI) is completed. A successful negotiation of either |
||||
* encryption layer sets both flags, but a rejected negotiation sets only the |
||||
* flag for that layer, since the client may wish to try the other one. We |
||||
* should make no assumption here about the order in which the client may make |
||||
* requests. |
||||
*/ |
||||
static int |
||||
ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done) |
||||
{ |
||||
int32 len; |
||||
char *buf; |
||||
ProtocolVersion proto; |
||||
MemoryContext oldcontext; |
||||
|
||||
pq_startmsgread(); |
||||
|
||||
/*
|
||||
* Grab the first byte of the length word separately, so that we can tell |
||||
* whether we have no data at all or an incomplete packet. (This might |
||||
* sound inefficient, but it's not really, because of buffering in |
||||
* pqcomm.c.) |
||||
*/ |
||||
if (pq_getbytes((char *) &len, 1) == EOF) |
||||
{ |
||||
/*
|
||||
* If we get no data at all, don't clutter the log with a complaint; |
||||
* such cases often occur for legitimate reasons. An example is that |
||||
* we might be here after responding to NEGOTIATE_SSL_CODE, and if the |
||||
* client didn't like our response, it'll probably just drop the |
||||
* connection. Service-monitoring software also often just opens and |
||||
* closes a connection without sending anything. (So do port |
||||
* scanners, which may be less benign, but it's not really our job to |
||||
* notice those.) |
||||
*/ |
||||
return STATUS_ERROR; |
||||
} |
||||
|
||||
if (pq_getbytes(((char *) &len) + 1, 3) == EOF) |
||||
{ |
||||
/* Got a partial length word, so bleat about that */ |
||||
if (!ssl_done && !gss_done) |
||||
ereport(COMMERROR, |
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION), |
||||
errmsg("incomplete startup packet"))); |
||||
return STATUS_ERROR; |
||||
} |
||||
|
||||
len = pg_ntoh32(len); |
||||
len -= 4; |
||||
|
||||
if (len < (int32) sizeof(ProtocolVersion) || |
||||
len > MAX_STARTUP_PACKET_LENGTH) |
||||
{ |
||||
ereport(COMMERROR, |
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION), |
||||
errmsg("invalid length of startup packet"))); |
||||
return STATUS_ERROR; |
||||
} |
||||
|
||||
/*
|
||||
* Allocate space to hold the startup packet, plus one extra byte that's |
||||
* initialized to be zero. This ensures we will have null termination of |
||||
* all strings inside the packet. |
||||
*/ |
||||
buf = palloc(len + 1); |
||||
buf[len] = '\0'; |
||||
|
||||
if (pq_getbytes(buf, len) == EOF) |
||||
{ |
||||
ereport(COMMERROR, |
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION), |
||||
errmsg("incomplete startup packet"))); |
||||
return STATUS_ERROR; |
||||
} |
||||
pq_endmsgread(); |
||||
|
||||
/*
|
||||
* The first field is either a protocol version number or a special |
||||
* request code. |
||||
*/ |
||||
port->proto = proto = pg_ntoh32(*((ProtocolVersion *) buf)); |
||||
|
||||
if (proto == CANCEL_REQUEST_CODE) |
||||
{ |
||||
CancelRequestPacket *canc; |
||||
int backendPID; |
||||
int32 cancelAuthCode; |
||||
|
||||
if (len != sizeof(CancelRequestPacket)) |
||||
{ |
||||
ereport(COMMERROR, |
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION), |
||||
errmsg("invalid length of startup packet"))); |
||||
return STATUS_ERROR; |
||||
} |
||||
canc = (CancelRequestPacket *) buf; |
||||
backendPID = (int) pg_ntoh32(canc->backendPID); |
||||
cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode); |
||||
|
||||
processCancelRequest(backendPID, cancelAuthCode); |
||||
/* Not really an error, but we don't want to proceed further */ |
||||
return STATUS_ERROR; |
||||
} |
||||
|
||||
if (proto == NEGOTIATE_SSL_CODE && !ssl_done) |
||||
{ |
||||
char SSLok; |
||||
|
||||
#ifdef USE_SSL |
||||
/* No SSL when disabled or on Unix sockets */ |
||||
if (!LoadedSSL || port->laddr.addr.ss_family == AF_UNIX) |
||||
SSLok = 'N'; |
||||
else |
||||
SSLok = 'S'; /* Support for SSL */ |
||||
#else |
||||
SSLok = 'N'; /* No support for SSL */ |
||||
#endif |
||||
|
||||
retry1: |
||||
if (send(port->sock, &SSLok, 1, 0) != 1) |
||||
{ |
||||
if (errno == EINTR) |
||||
goto retry1; /* if interrupted, just retry */ |
||||
ereport(COMMERROR, |
||||
(errcode_for_socket_access(), |
||||
errmsg("failed to send SSL negotiation response: %m"))); |
||||
return STATUS_ERROR; /* close the connection */ |
||||
} |
||||
|
||||
#ifdef USE_SSL |
||||
if (SSLok == 'S' && secure_open_server(port) == -1) |
||||
return STATUS_ERROR; |
||||
#endif |
||||
|
||||
/*
|
||||
* At this point we should have no data already buffered. If we do, |
||||
* it was received before we performed the SSL handshake, so it wasn't |
||||
* encrypted and indeed may have been injected by a man-in-the-middle. |
||||
* We report this case to the client. |
||||
*/ |
||||
if (pq_buffer_has_data()) |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION), |
||||
errmsg("received unencrypted data after SSL request"), |
||||
errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack."))); |
||||
|
||||
/*
|
||||
* regular startup packet, cancel, etc packet should follow, but not |
||||
* another SSL negotiation request, and a GSS request should only |
||||
* follow if SSL was rejected (client may negotiate in either order) |
||||
*/ |
||||
return ProcessStartupPacket(port, true, SSLok == 'S'); |
||||
} |
||||
else if (proto == NEGOTIATE_GSS_CODE && !gss_done) |
||||
{ |
||||
char GSSok = 'N'; |
||||
|
||||
#ifdef ENABLE_GSS |
||||
/* No GSSAPI encryption when on Unix socket */ |
||||
if (port->laddr.addr.ss_family != AF_UNIX) |
||||
GSSok = 'G'; |
||||
#endif |
||||
|
||||
while (send(port->sock, &GSSok, 1, 0) != 1) |
||||
{ |
||||
if (errno == EINTR) |
||||
continue; |
||||
ereport(COMMERROR, |
||||
(errcode_for_socket_access(), |
||||
errmsg("failed to send GSSAPI negotiation response: %m"))); |
||||
return STATUS_ERROR; /* close the connection */ |
||||
} |
||||
|
||||
#ifdef ENABLE_GSS |
||||
if (GSSok == 'G' && secure_open_gssapi(port) == -1) |
||||
return STATUS_ERROR; |
||||
#endif |
||||
|
||||
/*
|
||||
* At this point we should have no data already buffered. If we do, |
||||
* it was received before we performed the GSS handshake, so it wasn't |
||||
* encrypted and indeed may have been injected by a man-in-the-middle. |
||||
* We report this case to the client. |
||||
*/ |
||||
if (pq_buffer_has_data()) |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION), |
||||
errmsg("received unencrypted data after GSSAPI encryption request"), |
||||
errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack."))); |
||||
|
||||
/*
|
||||
* regular startup packet, cancel, etc packet should follow, but not |
||||
* another GSS negotiation request, and an SSL request should only |
||||
* follow if GSS was rejected (client may negotiate in either order) |
||||
*/ |
||||
return ProcessStartupPacket(port, GSSok == 'G', true); |
||||
} |
||||
|
||||
/* Could add additional special packet types here */ |
||||
|
||||
/*
|
||||
* Set FrontendProtocol now so that ereport() knows what format to send if |
||||
* we fail during startup. |
||||
*/ |
||||
FrontendProtocol = proto; |
||||
|
||||
/* Check that the major protocol version is in range. */ |
||||
if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) || |
||||
PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST)) |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u", |
||||
PG_PROTOCOL_MAJOR(proto), PG_PROTOCOL_MINOR(proto), |
||||
PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST), |
||||
PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST), |
||||
PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)))); |
||||
|
||||
/*
|
||||
* Now fetch parameters out of startup packet and save them into the Port |
||||
* structure. |
||||
*/ |
||||
oldcontext = MemoryContextSwitchTo(TopMemoryContext); |
||||
|
||||
/* Handle protocol version 3 startup packet */ |
||||
{ |
||||
int32 offset = sizeof(ProtocolVersion); |
||||
List *unrecognized_protocol_options = NIL; |
||||
|
||||
/*
|
||||
* Scan packet body for name/option pairs. We can assume any string |
||||
* beginning within the packet body is null-terminated, thanks to |
||||
* zeroing extra byte above. |
||||
*/ |
||||
port->guc_options = NIL; |
||||
|
||||
while (offset < len) |
||||
{ |
||||
char *nameptr = buf + offset; |
||||
int32 valoffset; |
||||
char *valptr; |
||||
|
||||
if (*nameptr == '\0') |
||||
break; /* found packet terminator */ |
||||
valoffset = offset + strlen(nameptr) + 1; |
||||
if (valoffset >= len) |
||||
break; /* missing value, will complain below */ |
||||
valptr = buf + valoffset; |
||||
|
||||
if (strcmp(nameptr, "database") == 0) |
||||
port->database_name = pstrdup(valptr); |
||||
else if (strcmp(nameptr, "user") == 0) |
||||
port->user_name = pstrdup(valptr); |
||||
else if (strcmp(nameptr, "options") == 0) |
||||
port->cmdline_options = pstrdup(valptr); |
||||
else if (strcmp(nameptr, "replication") == 0) |
||||
{ |
||||
/*
|
||||
* Due to backward compatibility concerns the replication |
||||
* parameter is a hybrid beast which allows the value to be |
||||
* either boolean or the string 'database'. The latter |
||||
* connects to a specific database which is e.g. required for |
||||
* logical decoding while. |
||||
*/ |
||||
if (strcmp(valptr, "database") == 0) |
||||
{ |
||||
am_walsender = true; |
||||
am_db_walsender = true; |
||||
} |
||||
else if (!parse_bool(valptr, &am_walsender)) |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
||||
errmsg("invalid value for parameter \"%s\": \"%s\"", |
||||
"replication", |
||||
valptr), |
||||
errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\"."))); |
||||
} |
||||
else if (strncmp(nameptr, "_pq_.", 5) == 0) |
||||
{ |
||||
/*
|
||||
* Any option beginning with _pq_. is reserved for use as a |
||||
* protocol-level option, but at present no such options are |
||||
* defined. |
||||
*/ |
||||
unrecognized_protocol_options = |
||||
lappend(unrecognized_protocol_options, pstrdup(nameptr)); |
||||
} |
||||
else |
||||
{ |
||||
/* Assume it's a generic GUC option */ |
||||
port->guc_options = lappend(port->guc_options, |
||||
pstrdup(nameptr)); |
||||
port->guc_options = lappend(port->guc_options, |
||||
pstrdup(valptr)); |
||||
|
||||
/*
|
||||
* Copy application_name to port if we come across it. This |
||||
* is done so we can log the application_name in the |
||||
* connection authorization message. Note that the GUC would |
||||
* be used but we haven't gone through GUC setup yet. |
||||
*/ |
||||
if (strcmp(nameptr, "application_name") == 0) |
||||
{ |
||||
port->application_name = pg_clean_ascii(valptr, 0); |
||||
} |
||||
} |
||||
offset = valoffset + strlen(valptr) + 1; |
||||
} |
||||
|
||||
/*
|
||||
* If we didn't find a packet terminator exactly at the end of the |
||||
* given packet length, complain. |
||||
*/ |
||||
if (offset != len - 1) |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_PROTOCOL_VIOLATION), |
||||
errmsg("invalid startup packet layout: expected terminator as last byte"))); |
||||
|
||||
/*
|
||||
* If the client requested a newer protocol version or if the client |
||||
* requested any protocol options we didn't recognize, let them know |
||||
* the newest minor protocol version we do support and the names of |
||||
* any unrecognized options. |
||||
*/ |
||||
if (PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) || |
||||
unrecognized_protocol_options != NIL) |
||||
SendNegotiateProtocolVersion(unrecognized_protocol_options); |
||||
} |
||||
|
||||
/* Check a user name was given. */ |
||||
if (port->user_name == NULL || port->user_name[0] == '\0') |
||||
ereport(FATAL, |
||||
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), |
||||
errmsg("no PostgreSQL user name specified in startup packet"))); |
||||
|
||||
/* The database defaults to the user name. */ |
||||
if (port->database_name == NULL || port->database_name[0] == '\0') |
||||
port->database_name = pstrdup(port->user_name); |
||||
|
||||
if (am_walsender) |
||||
MyBackendType = B_WAL_SENDER; |
||||
else |
||||
MyBackendType = B_BACKEND; |
||||
|
||||
/*
|
||||
* Normal walsender backends, e.g. for streaming replication, are not |
||||
* connected to a particular database. But walsenders used for logical |
||||
* replication need to connect to a specific database. We allow streaming |
||||
* replication commands to be issued even if connected to a database as it |
||||
* can make sense to first make a basebackup and then stream changes |
||||
* starting from that. |
||||
*/ |
||||
if (am_walsender && !am_db_walsender) |
||||
port->database_name[0] = '\0'; |
||||
|
||||
/*
|
||||
* Done filling the Port structure |
||||
*/ |
||||
MemoryContextSwitchTo(oldcontext); |
||||
|
||||
return STATUS_OK; |
||||
} |
||||
|
||||
/*
|
||||
* Send a NegotiateProtocolVersion to the client. This lets the client know |
||||
* that they have requested a newer minor protocol version than we are able |
||||
* to speak. We'll speak the highest version we know about; the client can, |
||||
* of course, abandon the connection if that's a problem. |
||||
* |
||||
* We also include in the response a list of protocol options we didn't |
||||
* understand. This allows clients to include optional parameters that might |
||||
* be present either in newer protocol versions or third-party protocol |
||||
* extensions without fear of having to reconnect if those options are not |
||||
* understood, while at the same time making certain that the client is aware |
||||
* of which options were actually accepted. |
||||
*/ |
||||
static void |
||||
SendNegotiateProtocolVersion(List *unrecognized_protocol_options) |
||||
{ |
||||
StringInfoData buf; |
||||
ListCell *lc; |
||||
|
||||
pq_beginmessage(&buf, PqMsg_NegotiateProtocolVersion); |
||||
pq_sendint32(&buf, PG_PROTOCOL_LATEST); |
||||
pq_sendint32(&buf, list_length(unrecognized_protocol_options)); |
||||
foreach(lc, unrecognized_protocol_options) |
||||
pq_sendstring(&buf, lfirst(lc)); |
||||
pq_endmessage(&buf); |
||||
|
||||
/* no need to flush, some other message will follow */ |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* SIGTERM while processing startup packet. |
||||
* |
||||
* Running proc_exit() from a signal handler would be quite unsafe. |
||||
* However, since we have not yet touched shared memory, we can just |
||||
* pull the plug and exit without running any atexit handlers. |
||||
* |
||||
* One might be tempted to try to send a message, or log one, indicating |
||||
* why we are disconnecting. However, that would be quite unsafe in itself. |
||||
* Also, it seems undesirable to provide clues about the database's state |
||||
* to a client that has not yet completed authentication, or even sent us |
||||
* a startup packet. |
||||
*/ |
||||
static void |
||||
process_startup_packet_die(SIGNAL_ARGS) |
||||
{ |
||||
_exit(1); |
||||
} |
||||
|
||||
/*
|
||||
* Timeout while processing startup packet. |
||||
* As for process_startup_packet_die(), we exit via _exit(1). |
||||
*/ |
||||
static void |
||||
StartupPacketTimeoutHandler(void) |
||||
{ |
||||
_exit(1); |
||||
} |
||||
@ -0,0 +1,41 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* backend_startup.h |
||||
* prototypes for backend_startup.c. |
||||
* |
||||
* |
||||
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* src/include/tcop/backend_startup.h |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef BACKEND_STARTUP_H |
||||
#define BACKEND_STARTUP_H |
||||
|
||||
/*
|
||||
* CAC_state is passed from postmaster to the backend process, to indicate |
||||
* whether the connection should be accepted, or if the process should just |
||||
* send an error to the client and close the connection. Note that the |
||||
* connection can fail for various reasons even if postmaster passed CAC_OK. |
||||
*/ |
||||
typedef enum CAC_state |
||||
{ |
||||
CAC_OK, |
||||
CAC_STARTUP, |
||||
CAC_SHUTDOWN, |
||||
CAC_RECOVERY, |
||||
CAC_NOTCONSISTENT, |
||||
CAC_TOOMANY, |
||||
} CAC_state; |
||||
|
||||
/* Information passed from postmaster to backend process in 'startup_data' */ |
||||
typedef struct BackendStartupData |
||||
{ |
||||
CAC_state canAcceptConnections; |
||||
} BackendStartupData; |
||||
|
||||
extern void BackendMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn(); |
||||
|
||||
#endif /* BACKEND_STARTUP_H */ |
||||
Loading…
Reference in new issue