LCOV - code coverage report
Current view: top level - src/bin/pg_basebackup - streamutil.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 71.6 % 331 237
Test Date: 2026-09-07 11:15:55 Functions: 100.0 % 16 16
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 62.9 % 194 122

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * streamutil.c - utility functions for pg_basebackup, pg_receivewal and
       4                 :             :  *                  pg_recvlogical
       5                 :             :  *
       6                 :             :  * Author: Magnus Hagander <magnus@hagander.net>
       7                 :             :  *
       8                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *        src/bin/pg_basebackup/streamutil.c
      12                 :             :  *-------------------------------------------------------------------------
      13                 :             :  */
      14                 :             : 
      15                 :             : #include "postgres_fe.h"
      16                 :             : 
      17                 :             : #include <sys/time.h>
      18                 :             : #include <unistd.h>
      19                 :             : 
      20                 :             : #include "access/xlog_internal.h"
      21                 :             : #include "common/connect.h"
      22                 :             : #include "common/file_perm.h"
      23                 :             : #include "common/logging.h"
      24                 :             : #include "common/pg_parse_lsn.h"
      25                 :             : #include "common/string.h"
      26                 :             : #include "datatype/timestamp.h"
      27                 :             : #include "port/pg_bswap.h"
      28                 :             : #include "pqexpbuffer.h"
      29                 :             : #include "streamutil.h"
      30                 :             : 
      31                 :             : #define ERRCODE_DUPLICATE_OBJECT  "42710"
      32                 :             : 
      33                 :             : int         WalSegSz;
      34                 :             : 
      35                 :             : static bool RetrieveDataDirCreatePerm(PGconn *conn);
      36                 :             : 
      37                 :             : /* SHOW command for replication connection was introduced in version 10 */
      38                 :             : #define MINIMUM_VERSION_FOR_SHOW_CMD 100000
      39                 :             : 
      40                 :             : /*
      41                 :             :  * Group access is supported from version 11.
      42                 :             :  */
      43                 :             : #define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
      44                 :             : 
      45                 :             : const char *progname;
      46                 :             : char       *connection_string = NULL;
      47                 :             : char       *dbhost = NULL;
      48                 :             : char       *dbuser = NULL;
      49                 :             : char       *dbport = NULL;
      50                 :             : char       *dbname = NULL;
      51                 :             : int         dbgetpassword = 0;  /* 0=auto, -1=never, 1=always */
      52                 :             : static char *password = NULL;
      53                 :             : PGconn     *conn = NULL;
      54                 :             : 
      55                 :             : /*
      56                 :             :  * Connect to the server. Returns a valid PGconn pointer if connected,
      57                 :             :  * or NULL on non-permanent error. On permanent error, the function will
      58                 :             :  * call exit(1) directly.
      59                 :             :  */
      60                 :             : PGconn *
      61                 :         448 : GetConnection(void)
      62                 :             : {
      63                 :             :     PGconn     *tmpconn;
      64                 :         448 :     int         argcount = 7;   /* dbname, replication, fallback_app_name,
      65                 :             :                                  * host, user, port, password */
      66                 :             :     int         i;
      67                 :             :     const char **keywords;
      68                 :             :     const char **values;
      69                 :             :     const char *tmpparam;
      70                 :             :     bool        need_password;
      71                 :         448 :     PQconninfoOption *conn_opts = NULL;
      72                 :             :     PQconninfoOption *conn_opt;
      73                 :         448 :     char       *err_msg = NULL;
      74                 :             : 
      75                 :             :     /*
      76                 :             :      * pg_recvlogical uses dbname only; others use connection_string only.
      77                 :             :      * (Note: both variables will be NULL if there's no command line options.)
      78                 :             :      */
      79                 :             :     Assert(dbname == NULL || connection_string == NULL);
      80                 :             : 
      81                 :             :     /*
      82                 :             :      * Merge the connection info inputs given in form of connection string,
      83                 :             :      * options and default values (dbname=replication, replication=true, etc.)
      84                 :             :      */
      85                 :         448 :     i = 0;
      86         [ +  + ]:         448 :     if (connection_string)
      87                 :             :     {
      88                 :           6 :         conn_opts = PQconninfoParse(connection_string, &err_msg);
      89         [ -  + ]:           6 :         if (conn_opts == NULL)
      90                 :           0 :             pg_fatal("%s", err_msg);
      91                 :             : 
      92         [ +  + ]:         318 :         for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
      93                 :             :         {
      94   [ +  +  +  - ]:         312 :             if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
      95                 :          10 :                 argcount++;
      96                 :             :         }
      97                 :             : 
      98                 :           6 :         keywords = pg_malloc0_array(const char *, argcount + 1);
      99                 :           6 :         values = pg_malloc0_array(const char *, argcount + 1);
     100                 :             : 
     101                 :             :         /*
     102                 :             :          * Set dbname here already, so it can be overridden by a dbname in the
     103                 :             :          * connection string.
     104                 :             :          */
     105                 :           6 :         keywords[i] = "dbname";
     106                 :           6 :         values[i] = "replication";
     107                 :           6 :         i++;
     108                 :             : 
     109         [ +  + ]:         318 :         for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++)
     110                 :             :         {
     111   [ +  +  +  - ]:         312 :             if (conn_opt->val != NULL && conn_opt->val[0] != '\0')
     112                 :             :             {
     113                 :          10 :                 keywords[i] = conn_opt->keyword;
     114                 :          10 :                 values[i] = conn_opt->val;
     115                 :          10 :                 i++;
     116                 :             :             }
     117                 :             :         }
     118                 :             :     }
     119                 :             :     else
     120                 :             :     {
     121                 :         442 :         keywords = pg_malloc0_array(const char *, argcount + 1);
     122                 :         442 :         values = pg_malloc0_array(const char *, argcount + 1);
     123                 :         442 :         keywords[i] = "dbname";
     124         [ +  + ]:         442 :         values[i] = (dbname == NULL) ? "replication" : dbname;
     125                 :         442 :         i++;
     126                 :             :     }
     127                 :             : 
     128                 :         448 :     keywords[i] = "replication";
     129         [ +  + ]:         448 :     values[i] = (dbname == NULL) ? "true" : "database";
     130                 :         448 :     i++;
     131                 :         448 :     keywords[i] = "fallback_application_name";
     132                 :         448 :     values[i] = progname;
     133                 :         448 :     i++;
     134                 :             : 
     135         [ +  + ]:         448 :     if (dbhost)
     136                 :             :     {
     137                 :         181 :         keywords[i] = "host";
     138                 :         181 :         values[i] = dbhost;
     139                 :         181 :         i++;
     140                 :             :     }
     141         [ +  + ]:         448 :     if (dbuser)
     142                 :             :     {
     143                 :           7 :         keywords[i] = "user";
     144                 :           7 :         values[i] = dbuser;
     145                 :           7 :         i++;
     146                 :             :     }
     147         [ +  + ]:         448 :     if (dbport)
     148                 :             :     {
     149                 :         181 :         keywords[i] = "port";
     150                 :         181 :         values[i] = dbport;
     151                 :         181 :         i++;
     152                 :             :     }
     153                 :             : 
     154                 :             :     /* If -W was given, force prompt for password, but only the first time */
     155   [ -  +  -  - ]:         448 :     need_password = (dbgetpassword == 1 && !password);
     156                 :             : 
     157                 :             :     do
     158                 :             :     {
     159                 :             :         /* Get a new password if appropriate */
     160         [ -  + ]:         448 :         if (need_password)
     161                 :             :         {
     162                 :           0 :             free(password);
     163                 :           0 :             password = simple_prompt("Password: ", false);
     164                 :           0 :             need_password = false;
     165                 :             :         }
     166                 :             : 
     167                 :             :         /* Use (or reuse, on a subsequent connection) password if we have it */
     168         [ -  + ]:         448 :         if (password)
     169                 :             :         {
     170                 :           0 :             keywords[i] = "password";
     171                 :           0 :             values[i] = password;
     172                 :             :         }
     173                 :             :         else
     174                 :             :         {
     175                 :         448 :             keywords[i] = NULL;
     176                 :         448 :             values[i] = NULL;
     177                 :             :         }
     178                 :             : 
     179                 :             :         /*
     180                 :             :          * Only expand dbname when we did not already parse the argument as a
     181                 :             :          * connection string ourselves.
     182                 :             :          */
     183                 :         448 :         tmpconn = PQconnectdbParams(keywords, values, !connection_string);
     184                 :             : 
     185                 :             :         /*
     186                 :             :          * If there is too little memory even to allocate the PGconn object
     187                 :             :          * and PQconnectdbParams returns NULL, we call exit(1) directly.
     188                 :             :          */
     189         [ -  + ]:         448 :         if (!tmpconn)
     190                 :           0 :             pg_fatal("could not connect to server");
     191                 :             : 
     192                 :             :         /* If we need a password and -w wasn't given, loop back and get one */
     193   [ +  +  -  + ]:         450 :         if (PQstatus(tmpconn) == CONNECTION_BAD &&
     194                 :           2 :             PQconnectionNeedsPassword(tmpconn) &&
     195         [ #  # ]:           0 :             dbgetpassword != -1)
     196                 :             :         {
     197                 :           0 :             PQfinish(tmpconn);
     198                 :           0 :             need_password = true;
     199                 :             :         }
     200                 :             :     }
     201         [ -  + ]:         448 :     while (need_password);
     202                 :             : 
     203         [ +  + ]:         448 :     if (PQstatus(tmpconn) != CONNECTION_OK)
     204                 :             :     {
     205                 :           2 :         pg_log_error("%s", PQerrorMessage(tmpconn));
     206                 :           2 :         PQfinish(tmpconn);
     207                 :           2 :         pg_free(values);
     208                 :           2 :         pg_free(keywords);
     209                 :           2 :         PQconninfoFree(conn_opts);
     210                 :           2 :         return NULL;
     211                 :             :     }
     212                 :             : 
     213                 :             :     /* Connection ok! */
     214                 :         446 :     pg_free(values);
     215                 :         446 :     pg_free(keywords);
     216                 :         446 :     PQconninfoFree(conn_opts);
     217                 :             : 
     218                 :             :     /*
     219                 :             :      * Set always-secure search path, so malicious users can't get control.
     220                 :             :      * The capacity to run normal SQL queries was added in PostgreSQL 10, so
     221                 :             :      * the search path cannot be changed (by us or attackers) on earlier
     222                 :             :      * versions.
     223                 :             :      */
     224   [ +  +  +  - ]:         446 :     if (dbname != NULL && PQserverVersion(tmpconn) >= 100000)
     225                 :             :     {
     226                 :             :         PGresult   *res;
     227                 :             : 
     228                 :          64 :         res = PQexec(tmpconn, ALWAYS_SECURE_SEARCH_PATH_SQL);
     229         [ -  + ]:          64 :         if (PQresultStatus(res) != PGRES_TUPLES_OK)
     230                 :             :         {
     231                 :           0 :             pg_log_error("could not clear \"search_path\": %s",
     232                 :             :                          PQerrorMessage(tmpconn));
     233                 :           0 :             PQclear(res);
     234                 :           0 :             PQfinish(tmpconn);
     235                 :           0 :             exit(1);
     236                 :             :         }
     237                 :          64 :         PQclear(res);
     238                 :             :     }
     239                 :             : 
     240                 :             :     /*
     241                 :             :      * Ensure we have the same value of integer_datetimes (now always "on") as
     242                 :             :      * the server we are connecting to.
     243                 :             :      */
     244                 :         446 :     tmpparam = PQparameterStatus(tmpconn, "integer_datetimes");
     245         [ -  + ]:         446 :     if (!tmpparam)
     246                 :             :     {
     247                 :           0 :         pg_log_error("could not determine server setting for \"integer_datetimes\"");
     248                 :           0 :         PQfinish(tmpconn);
     249                 :           0 :         exit(1);
     250                 :             :     }
     251                 :             : 
     252         [ -  + ]:         446 :     if (strcmp(tmpparam, "on") != 0)
     253                 :             :     {
     254                 :           0 :         pg_log_error("\"integer_datetimes\" compile flag does not match server");
     255                 :           0 :         PQfinish(tmpconn);
     256                 :           0 :         exit(1);
     257                 :             :     }
     258                 :             : 
     259                 :             :     /*
     260                 :             :      * Retrieve the source data directory mode and use it to construct a umask
     261                 :             :      * for creating directories and files.
     262                 :             :      */
     263         [ -  + ]:         446 :     if (!RetrieveDataDirCreatePerm(tmpconn))
     264                 :             :     {
     265                 :           0 :         PQfinish(tmpconn);
     266                 :           0 :         exit(1);
     267                 :             :     }
     268                 :             : 
     269                 :         446 :     return tmpconn;
     270                 :             : }
     271                 :             : 
     272                 :             : /*
     273                 :             :  * From version 10, explicitly set wal segment size using SHOW wal_segment_size
     274                 :             :  * since ControlFile is not accessible here.
     275                 :             :  */
     276                 :             : bool
     277                 :         215 : RetrieveWalSegSize(PGconn *conn)
     278                 :             : {
     279                 :             :     PGresult   *res;
     280                 :             :     char        xlog_unit[3];
     281                 :             :     int         xlog_val,
     282                 :         215 :                 multiplier = 1;
     283                 :             : 
     284                 :             :     /* check connection existence */
     285                 :             :     Assert(conn != NULL);
     286                 :             : 
     287                 :             :     /* for previous versions set the default xlog seg size */
     288         [ -  + ]:         215 :     if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_SHOW_CMD)
     289                 :             :     {
     290                 :           0 :         WalSegSz = DEFAULT_XLOG_SEG_SIZE;
     291                 :           0 :         return true;
     292                 :             :     }
     293                 :             : 
     294                 :         215 :     res = PQexec(conn, "SHOW wal_segment_size");
     295         [ -  + ]:         215 :     if (PQresultStatus(res) != PGRES_TUPLES_OK)
     296                 :             :     {
     297                 :           0 :         pg_log_error("could not send replication command \"%s\": %s",
     298                 :             :                      "SHOW wal_segment_size", PQerrorMessage(conn));
     299                 :             : 
     300                 :           0 :         PQclear(res);
     301                 :           0 :         return false;
     302                 :             :     }
     303   [ +  -  -  + ]:         215 :     if (PQntuples(res) != 1 || PQnfields(res) < 1)
     304                 :             :     {
     305                 :           0 :         pg_log_error("could not fetch WAL segment size: got %d rows and %d fields, expected %d rows and %d or more fields",
     306                 :             :                      PQntuples(res), PQnfields(res), 1, 1);
     307                 :             : 
     308                 :           0 :         PQclear(res);
     309                 :           0 :         return false;
     310                 :             :     }
     311                 :             : 
     312                 :             :     /* fetch xlog value and unit from the result */
     313         [ -  + ]:         215 :     if (sscanf(PQgetvalue(res, 0, 0), "%d%2s", &xlog_val, xlog_unit) != 2)
     314                 :             :     {
     315                 :           0 :         pg_log_error("WAL segment size could not be parsed");
     316                 :           0 :         PQclear(res);
     317                 :           0 :         return false;
     318                 :             :     }
     319                 :             : 
     320                 :         215 :     PQclear(res);
     321                 :             : 
     322                 :             :     /* set the multiplier based on unit to convert xlog_val to bytes */
     323         [ +  - ]:         215 :     if (strcmp(xlog_unit, "MB") == 0)
     324                 :         215 :         multiplier = 1024 * 1024;
     325         [ #  # ]:           0 :     else if (strcmp(xlog_unit, "GB") == 0)
     326                 :           0 :         multiplier = 1024 * 1024 * 1024;
     327                 :             : 
     328                 :             :     /* convert and set WalSegSz */
     329                 :         215 :     WalSegSz = xlog_val * multiplier;
     330                 :             : 
     331   [ +  -  +  -  :         215 :     if (!IsValidWalSegSize(WalSegSz))
             +  -  -  + ]
     332                 :             :     {
     333                 :           0 :         pg_log_error(ngettext("remote server reported invalid WAL segment size (%d byte)",
     334                 :             :                               "remote server reported invalid WAL segment size (%d bytes)",
     335                 :             :                               WalSegSz),
     336                 :             :                      WalSegSz);
     337                 :           0 :         pg_log_error_detail("The WAL segment size must be a power of two between 1 MB and 1 GB.");
     338                 :           0 :         return false;
     339                 :             :     }
     340                 :             : 
     341                 :         215 :     return true;
     342                 :             : }
     343                 :             : 
     344                 :             : /*
     345                 :             :  * RetrieveDataDirCreatePerm
     346                 :             :  *
     347                 :             :  * This function is used to determine the privileges on the server's PG data
     348                 :             :  * directory and, based on that, set what the permissions will be for
     349                 :             :  * directories and files we create.
     350                 :             :  *
     351                 :             :  * PG11 added support for (optionally) group read/execute rights to be set on
     352                 :             :  * the data directory.  Prior to PG11, only the owner was allowed to have rights
     353                 :             :  * on the data directory.
     354                 :             :  */
     355                 :             : static bool
     356                 :         446 : RetrieveDataDirCreatePerm(PGconn *conn)
     357                 :             : {
     358                 :             :     PGresult   *res;
     359                 :             :     int         data_directory_mode;
     360                 :             : 
     361                 :             :     /* check connection existence */
     362                 :             :     Assert(conn != NULL);
     363                 :             : 
     364                 :             :     /* for previous versions leave the default group access */
     365         [ -  + ]:         446 :     if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
     366                 :           0 :         return true;
     367                 :             : 
     368                 :         446 :     res = PQexec(conn, "SHOW data_directory_mode");
     369         [ -  + ]:         446 :     if (PQresultStatus(res) != PGRES_TUPLES_OK)
     370                 :             :     {
     371                 :           0 :         pg_log_error("could not send replication command \"%s\": %s",
     372                 :             :                      "SHOW data_directory_mode", PQerrorMessage(conn));
     373                 :             : 
     374                 :           0 :         PQclear(res);
     375                 :           0 :         return false;
     376                 :             :     }
     377   [ +  -  -  + ]:         446 :     if (PQntuples(res) != 1 || PQnfields(res) < 1)
     378                 :             :     {
     379                 :           0 :         pg_log_error("could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields",
     380                 :             :                      PQntuples(res), PQnfields(res), 1, 1);
     381                 :             : 
     382                 :           0 :         PQclear(res);
     383                 :           0 :         return false;
     384                 :             :     }
     385                 :             : 
     386         [ -  + ]:         446 :     if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
     387                 :             :     {
     388                 :           0 :         pg_log_error("group access flag could not be parsed: %s",
     389                 :             :                      PQgetvalue(res, 0, 0));
     390                 :             : 
     391                 :           0 :         PQclear(res);
     392                 :           0 :         return false;
     393                 :             :     }
     394                 :             : 
     395                 :         446 :     SetDataDirectoryCreatePerm(data_directory_mode);
     396                 :             : 
     397                 :         446 :     PQclear(res);
     398                 :         446 :     return true;
     399                 :             : }
     400                 :             : 
     401                 :             : /*
     402                 :             :  * Run IDENTIFY_SYSTEM through a given connection and give back to caller
     403                 :             :  * some result information if requested:
     404                 :             :  * - System identifier
     405                 :             :  * - Current timeline ID
     406                 :             :  * - Start LSN position
     407                 :             :  * - Database name (NULL in servers prior to 9.4)
     408                 :             :  */
     409                 :             : bool
     410                 :         453 : RunIdentifySystem(PGconn *conn, char **sysid, TimeLineID *starttli,
     411                 :             :                   XLogRecPtr *startpos, char **db_name)
     412                 :             : {
     413                 :             :     PGresult   *res;
     414                 :             : 
     415                 :             :     /* Check connection existence */
     416                 :             :     Assert(conn != NULL);
     417                 :             : 
     418                 :         453 :     res = PQexec(conn, "IDENTIFY_SYSTEM");
     419         [ -  + ]:         453 :     if (PQresultStatus(res) != PGRES_TUPLES_OK)
     420                 :             :     {
     421                 :           0 :         pg_log_error("could not send replication command \"%s\": %s",
     422                 :             :                      "IDENTIFY_SYSTEM", PQerrorMessage(conn));
     423                 :             : 
     424                 :           0 :         PQclear(res);
     425                 :           0 :         return false;
     426                 :             :     }
     427   [ +  -  -  + ]:         453 :     if (PQntuples(res) != 1 || PQnfields(res) < 3)
     428                 :             :     {
     429                 :           0 :         pg_log_error("could not identify system: got %d rows and %d fields, expected %d rows and %d or more fields",
     430                 :             :                      PQntuples(res), PQnfields(res), 1, 3);
     431                 :             : 
     432                 :           0 :         PQclear(res);
     433                 :           0 :         return false;
     434                 :             :     }
     435                 :             : 
     436                 :             :     /* Get system identifier */
     437         [ +  + ]:         453 :     if (sysid != NULL)
     438                 :         380 :         *sysid = pg_strdup(PQgetvalue(res, 0, 0));
     439                 :             : 
     440                 :             :     /* Get timeline ID to start streaming from */
     441         [ +  + ]:         453 :     if (starttli != NULL)
     442                 :         380 :         *starttli = atoi(PQgetvalue(res, 0, 1));
     443                 :             : 
     444                 :             :     /* Get LSN start position if necessary */
     445         [ +  + ]:         453 :     if (startpos != NULL)
     446                 :             :     {
     447         [ -  + ]:           7 :         if (!pg_parse_lsn(PQgetvalue(res, 0, 2), startpos))
     448                 :             :         {
     449                 :           0 :             pg_log_error("could not parse write-ahead log location \"%s\"",
     450                 :             :                          PQgetvalue(res, 0, 2));
     451                 :             : 
     452                 :           0 :             PQclear(res);
     453                 :           0 :             return false;
     454                 :             :         }
     455                 :             :     }
     456                 :             : 
     457                 :             :     /* Get database name, only available in 9.4 and newer versions */
     458         [ +  + ]:         453 :     if (db_name != NULL)
     459                 :             :     {
     460                 :          73 :         *db_name = NULL;
     461         [ +  - ]:          73 :         if (PQserverVersion(conn) >= 90400)
     462                 :             :         {
     463         [ -  + ]:          73 :             if (PQnfields(res) < 4)
     464                 :             :             {
     465                 :           0 :                 pg_log_error("could not identify system: got %d rows and %d fields, expected %d rows and %d or more fields",
     466                 :             :                              PQntuples(res), PQnfields(res), 1, 4);
     467                 :             : 
     468                 :           0 :                 PQclear(res);
     469                 :           0 :                 return false;
     470                 :             :             }
     471         [ +  + ]:          73 :             if (!PQgetisnull(res, 0, 3))
     472                 :          63 :                 *db_name = pg_strdup(PQgetvalue(res, 0, 3));
     473                 :             :         }
     474                 :             :     }
     475                 :             : 
     476                 :         453 :     PQclear(res);
     477                 :         453 :     return true;
     478                 :             : }
     479                 :             : 
     480                 :             : /*
     481                 :             :  * Run READ_REPLICATION_SLOT through a given connection and give back to
     482                 :             :  * caller some result information if requested for this slot:
     483                 :             :  * - Start LSN position, InvalidXLogRecPtr if unknown.
     484                 :             :  * - Current timeline ID, 0 if unknown.
     485                 :             :  * Returns false on failure, and true otherwise.
     486                 :             :  */
     487                 :             : bool
     488                 :           3 : GetSlotInformation(PGconn *conn, const char *slot_name,
     489                 :             :                    XLogRecPtr *restart_lsn, TimeLineID *restart_tli)
     490                 :             : {
     491                 :             :     PGresult   *res;
     492                 :             :     PQExpBuffer query;
     493                 :           3 :     XLogRecPtr  lsn_loc = InvalidXLogRecPtr;
     494                 :           3 :     TimeLineID  tli_loc = 0;
     495                 :             : 
     496         [ +  - ]:           3 :     if (restart_lsn)
     497                 :           3 :         *restart_lsn = lsn_loc;
     498         [ +  - ]:           3 :     if (restart_tli)
     499                 :           3 :         *restart_tli = tli_loc;
     500                 :             : 
     501                 :           3 :     query = createPQExpBuffer();
     502                 :           3 :     appendPQExpBufferStr(query, "READ_REPLICATION_SLOT ");
     503                 :           3 :     AppendQuotedIdentifier(query, slot_name);
     504                 :           3 :     res = PQexec(conn, query->data);
     505                 :           3 :     destroyPQExpBuffer(query);
     506                 :             : 
     507         [ -  + ]:           3 :     if (PQresultStatus(res) != PGRES_TUPLES_OK)
     508                 :             :     {
     509                 :           0 :         pg_log_error("could not send replication command \"%s\": %s",
     510                 :             :                      "READ_REPLICATION_SLOT", PQerrorMessage(conn));
     511                 :           0 :         PQclear(res);
     512                 :           0 :         return false;
     513                 :             :     }
     514                 :             : 
     515                 :             :     /* The command should always return precisely one tuple and three fields */
     516   [ +  -  -  + ]:           3 :     if (PQntuples(res) != 1 || PQnfields(res) != 3)
     517                 :             :     {
     518                 :           0 :         pg_log_error("could not read replication slot \"%s\": got %d rows and %d fields, expected %d rows and %d fields",
     519                 :             :                      slot_name, PQntuples(res), PQnfields(res), 1, 3);
     520                 :           0 :         PQclear(res);
     521                 :           0 :         return false;
     522                 :             :     }
     523                 :             : 
     524                 :             :     /*
     525                 :             :      * When the slot doesn't exist, the command returns a tuple with NULL
     526                 :             :      * values.  This checks only the slot type field.
     527                 :             :      */
     528         [ +  + ]:           3 :     if (PQgetisnull(res, 0, 0))
     529                 :             :     {
     530                 :           1 :         pg_log_error("replication slot \"%s\" does not exist", slot_name);
     531                 :           1 :         PQclear(res);
     532                 :           1 :         return false;
     533                 :             :     }
     534                 :             : 
     535                 :             :     /*
     536                 :             :      * Note that this cannot happen as READ_REPLICATION_SLOT supports only
     537                 :             :      * physical slots, but play it safe.
     538                 :             :      */
     539         [ -  + ]:           2 :     if (strcmp(PQgetvalue(res, 0, 0), "physical") != 0)
     540                 :             :     {
     541                 :           0 :         pg_log_error("expected a physical replication slot, got type \"%s\" instead",
     542                 :             :                      PQgetvalue(res, 0, 0));
     543                 :           0 :         PQclear(res);
     544                 :           0 :         return false;
     545                 :             :     }
     546                 :             : 
     547                 :             :     /* restart LSN */
     548         [ +  - ]:           2 :     if (!PQgetisnull(res, 0, 1))
     549                 :             :     {
     550         [ -  + ]:           2 :         if (!pg_parse_lsn(PQgetvalue(res, 0, 1), &lsn_loc))
     551                 :             :         {
     552                 :           0 :             pg_log_error("could not parse restart_lsn \"%s\" for replication slot \"%s\"",
     553                 :             :                          PQgetvalue(res, 0, 1), slot_name);
     554                 :           0 :             PQclear(res);
     555                 :           0 :             return false;
     556                 :             :         }
     557                 :             :     }
     558                 :             : 
     559                 :             :     /* current TLI */
     560         [ +  - ]:           2 :     if (!PQgetisnull(res, 0, 2))
     561                 :           2 :         tli_loc = (TimeLineID) atoll(PQgetvalue(res, 0, 2));
     562                 :             : 
     563                 :           2 :     PQclear(res);
     564                 :             : 
     565                 :             :     /* Assign results if requested */
     566         [ +  - ]:           2 :     if (restart_lsn)
     567                 :           2 :         *restart_lsn = lsn_loc;
     568         [ +  - ]:           2 :     if (restart_tli)
     569                 :           2 :         *restart_tli = tli_loc;
     570                 :             : 
     571                 :           2 :     return true;
     572                 :             : }
     573                 :             : 
     574                 :             : /*
     575                 :             :  * Create a replication slot for the given connection. This function
     576                 :             :  * returns true in case of success.
     577                 :             :  */
     578                 :             : bool
     579                 :         193 : CreateReplicationSlot(PGconn *conn, const char *slot_name, const char *plugin,
     580                 :             :                       bool is_temporary, bool is_physical, bool reserve_wal,
     581                 :             :                       bool slot_exists_ok, bool two_phase, bool failover)
     582                 :             : {
     583                 :             :     PQExpBuffer query;
     584                 :             :     PGresult   *res;
     585                 :         193 :     bool        use_new_option_syntax = (PQserverVersion(conn) >= 150000);
     586                 :             : 
     587                 :         193 :     query = createPQExpBuffer();
     588                 :             : 
     589                 :             :     Assert((is_physical && plugin == NULL) ||
     590                 :             :            (!is_physical && plugin != NULL));
     591                 :             :     Assert(!(two_phase && is_physical));
     592                 :             :     Assert(!(failover && is_physical));
     593                 :             :     Assert(slot_name != NULL);
     594                 :             : 
     595                 :             :     /* Build base portion of query */
     596                 :         193 :     appendPQExpBufferStr(query, "CREATE_REPLICATION_SLOT ");
     597                 :         193 :     AppendQuotedIdentifier(query, slot_name);
     598         [ +  + ]:         193 :     if (is_temporary)
     599                 :         157 :         appendPQExpBufferStr(query, " TEMPORARY");
     600         [ +  + ]:         193 :     if (is_physical)
     601                 :         161 :         appendPQExpBufferStr(query, " PHYSICAL");
     602                 :             :     else
     603                 :             :     {
     604                 :          32 :         appendPQExpBufferStr(query, " LOGICAL ");
     605                 :          32 :         AppendQuotedIdentifier(query, plugin);
     606                 :             :     }
     607                 :             : 
     608                 :             :     /* Add any requested options */
     609         [ +  - ]:         193 :     if (use_new_option_syntax)
     610                 :         193 :         appendPQExpBufferStr(query, " (");
     611         [ +  + ]:         193 :     if (is_physical)
     612                 :             :     {
     613         [ +  + ]:         161 :         if (reserve_wal)
     614                 :         160 :             AppendPlainCommandOption(query, use_new_option_syntax,
     615                 :             :                                      "RESERVE_WAL");
     616                 :             :     }
     617                 :             :     else
     618                 :             :     {
     619   [ +  +  +  - ]:          32 :         if (failover && PQserverVersion(conn) >= 170000)
     620                 :           1 :             AppendPlainCommandOption(query, use_new_option_syntax,
     621                 :             :                                      "FAILOVER");
     622                 :             : 
     623   [ +  +  +  - ]:          32 :         if (two_phase && PQserverVersion(conn) >= 150000)
     624                 :           1 :             AppendPlainCommandOption(query, use_new_option_syntax,
     625                 :             :                                      "TWO_PHASE");
     626                 :             : 
     627         [ +  - ]:          32 :         if (PQserverVersion(conn) >= 100000)
     628                 :             :         {
     629                 :             :             /* pg_recvlogical doesn't use an exported snapshot, so suppress */
     630         [ +  - ]:          32 :             if (use_new_option_syntax)
     631                 :          32 :                 AppendStringCommandOption(query, use_new_option_syntax,
     632                 :             :                                           "SNAPSHOT", "nothing");
     633                 :             :             else
     634                 :           0 :                 AppendPlainCommandOption(query, use_new_option_syntax,
     635                 :             :                                          "NOEXPORT_SNAPSHOT");
     636                 :             :         }
     637                 :             :     }
     638         [ +  - ]:         193 :     if (use_new_option_syntax)
     639                 :             :     {
     640                 :             :         /* Suppress option list if it would be empty, otherwise terminate */
     641         [ +  + ]:         193 :         if (query->data[query->len - 1] == '(')
     642                 :             :         {
     643                 :           1 :             query->len -= 2;
     644                 :           1 :             query->data[query->len] = '\0';
     645                 :             :         }
     646                 :             :         else
     647                 :         192 :             appendPQExpBufferChar(query, ')');
     648                 :             :     }
     649                 :             : 
     650                 :             :     /* Now run the query */
     651                 :         193 :     res = PQexec(conn, query->data);
     652         [ +  + ]:         193 :     if (PQresultStatus(res) != PGRES_TUPLES_OK)
     653                 :             :     {
     654                 :           1 :         const char *sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
     655                 :             : 
     656   [ -  +  -  - ]:           1 :         if (slot_exists_ok &&
     657                 :           0 :             sqlstate &&
     658         [ #  # ]:           0 :             strcmp(sqlstate, ERRCODE_DUPLICATE_OBJECT) == 0)
     659                 :             :         {
     660                 :           0 :             destroyPQExpBuffer(query);
     661                 :           0 :             PQclear(res);
     662                 :           0 :             return true;
     663                 :             :         }
     664                 :             :         else
     665                 :             :         {
     666                 :           1 :             pg_log_error("could not send replication command \"%s\": %s",
     667                 :             :                          query->data, PQerrorMessage(conn));
     668                 :             : 
     669                 :           1 :             destroyPQExpBuffer(query);
     670                 :           1 :             PQclear(res);
     671                 :           1 :             return false;
     672                 :             :         }
     673                 :             :     }
     674                 :             : 
     675   [ +  -  -  + ]:         192 :     if (PQntuples(res) != 1 || PQnfields(res) != 4)
     676                 :             :     {
     677                 :           0 :         pg_log_error("could not create replication slot \"%s\": got %d rows and %d fields, expected %d rows and %d fields",
     678                 :             :                      slot_name,
     679                 :             :                      PQntuples(res), PQnfields(res), 1, 4);
     680                 :             : 
     681                 :           0 :         destroyPQExpBuffer(query);
     682                 :           0 :         PQclear(res);
     683                 :           0 :         return false;
     684                 :             :     }
     685                 :             : 
     686                 :         192 :     destroyPQExpBuffer(query);
     687                 :         192 :     PQclear(res);
     688                 :         192 :     return true;
     689                 :             : }
     690                 :             : 
     691                 :             : /*
     692                 :             :  * Drop a replication slot for the given connection. This function
     693                 :             :  * returns true in case of success.
     694                 :             :  */
     695                 :             : bool
     696                 :           5 : DropReplicationSlot(PGconn *conn, const char *slot_name)
     697                 :             : {
     698                 :             :     PQExpBuffer query;
     699                 :             :     PGresult   *res;
     700                 :             : 
     701                 :             :     Assert(slot_name != NULL);
     702                 :             : 
     703                 :           5 :     query = createPQExpBuffer();
     704                 :             : 
     705                 :             :     /* Build query */
     706                 :           5 :     appendPQExpBufferStr(query, "DROP_REPLICATION_SLOT ");
     707                 :           5 :     AppendQuotedIdentifier(query, slot_name);
     708                 :           5 :     res = PQexec(conn, query->data);
     709         [ -  + ]:           5 :     if (PQresultStatus(res) != PGRES_COMMAND_OK)
     710                 :             :     {
     711                 :           0 :         pg_log_error("could not send replication command \"%s\": %s",
     712                 :             :                      query->data, PQerrorMessage(conn));
     713                 :             : 
     714                 :           0 :         destroyPQExpBuffer(query);
     715                 :           0 :         PQclear(res);
     716                 :           0 :         return false;
     717                 :             :     }
     718                 :             : 
     719   [ +  -  -  + ]:           5 :     if (PQntuples(res) != 0 || PQnfields(res) != 0)
     720                 :             :     {
     721                 :           0 :         pg_log_error("could not drop replication slot \"%s\": got %d rows and %d fields, expected %d rows and %d fields",
     722                 :             :                      slot_name,
     723                 :             :                      PQntuples(res), PQnfields(res), 0, 0);
     724                 :             : 
     725                 :           0 :         destroyPQExpBuffer(query);
     726                 :           0 :         PQclear(res);
     727                 :           0 :         return false;
     728                 :             :     }
     729                 :             : 
     730                 :           5 :     destroyPQExpBuffer(query);
     731                 :           5 :     PQclear(res);
     732                 :           5 :     return true;
     733                 :             : }
     734                 :             : 
     735                 :             : /*
     736                 :             :  * Append a suitably-quoted identifier or string literal to buf.
     737                 :             :  * "quote" should be either a double-quote or single-quote character.
     738                 :             :  *
     739                 :             :  * Caution: this quoting logic is sufficient for identifiers and literals
     740                 :             :  * in the replication grammar, but not always in regular SQL.  Specifically,
     741                 :             :  * it'd fail for a string literal if standard_conforming_strings is off.
     742                 :             :  */
     743                 :             : void
     744                 :        1416 : AppendQuotedString(PQExpBuffer buf, const char *str, char quote)
     745                 :             : {
     746                 :        1416 :     appendPQExpBufferChar(buf, quote);
     747         [ +  + ]:       19129 :     while (*str)
     748                 :             :     {
     749                 :       17713 :         char        c = *str++;
     750                 :             : 
     751         [ -  + ]:       17713 :         if (c == quote)
     752                 :           0 :             appendPQExpBufferChar(buf, c);
     753                 :       17713 :         appendPQExpBufferChar(buf, c);
     754                 :             :     }
     755                 :        1416 :     appendPQExpBufferChar(buf, quote);
     756                 :        1416 : }
     757                 :             : 
     758                 :             : /*
     759                 :             :  * Append a "plain" option - one with no value - to a server command that
     760                 :             :  * is being constructed.
     761                 :             :  *
     762                 :             :  * In the old syntax, all options were parser keywords, so you could just
     763                 :             :  * write things like SOME_COMMAND OPTION1 OPTION2 'opt2value' OPTION3 42. The
     764                 :             :  * new syntax uses a comma-separated list surrounded by parentheses, so the
     765                 :             :  * equivalent is SOME_COMMAND (OPTION1, OPTION2 'optvalue', OPTION3 42).
     766                 :             :  *
     767                 :             :  * Note: we assume option names do not require quotes.  Do not use this
     768                 :             :  * with option names coming from outside sources.
     769                 :             :  */
     770                 :             : void
     771                 :        1544 : AppendPlainCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
     772                 :             :                          const char *option_name)
     773                 :             : {
     774   [ +  +  +  + ]:        1544 :     if (buf->len > 0 && buf->data[buf->len - 1] != '(')
     775                 :             :     {
     776         [ +  - ]:        1145 :         if (use_new_option_syntax)
     777                 :        1145 :             appendPQExpBufferStr(buf, ", ");
     778                 :             :         else
     779                 :           0 :             appendPQExpBufferChar(buf, ' ');
     780                 :             :     }
     781                 :             : 
     782                 :        1544 :     appendPQExpBuffer(buf, " %s", option_name);
     783                 :        1544 : }
     784                 :             : 
     785                 :             : /*
     786                 :             :  * Append an option with an associated string value to a server command that
     787                 :             :  * is being constructed.
     788                 :             :  *
     789                 :             :  * See comments for AppendPlainCommandOption, above.
     790                 :             :  */
     791                 :             : void
     792                 :         909 : AppendStringCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
     793                 :             :                           const char *option_name, const char *option_value)
     794                 :             : {
     795                 :         909 :     AppendPlainCommandOption(buf, use_new_option_syntax, option_name);
     796                 :             : 
     797         [ +  - ]:         909 :     if (option_value != NULL)
     798                 :             :     {
     799                 :         909 :         appendPQExpBufferChar(buf, ' ');
     800                 :         909 :         AppendQuotedLiteral(buf, option_value);
     801                 :             :     }
     802                 :         909 : }
     803                 :             : 
     804                 :             : /*
     805                 :             :  * Append an option with an associated integer value to a server command that
     806                 :             :  * is being constructed.
     807                 :             :  *
     808                 :             :  * See comments for AppendPlainCommandOption, above.
     809                 :             :  */
     810                 :             : void
     811                 :         199 : AppendIntegerCommandOption(PQExpBuffer buf, bool use_new_option_syntax,
     812                 :             :                            const char *option_name, int32 option_value)
     813                 :             : {
     814                 :         199 :     AppendPlainCommandOption(buf, use_new_option_syntax, option_name);
     815                 :             : 
     816                 :         199 :     appendPQExpBuffer(buf, " %d", option_value);
     817                 :         199 : }
     818                 :             : 
     819                 :             : /*
     820                 :             :  * Frontend version of GetCurrentTimestamp(), since we are not linked with
     821                 :             :  * backend code.
     822                 :             :  */
     823                 :             : TimestampTz
     824                 :        1172 : feGetCurrentTimestamp(void)
     825                 :             : {
     826                 :             :     TimestampTz result;
     827                 :             :     struct timeval tp;
     828                 :             : 
     829                 :        1172 :     gettimeofday(&tp, NULL);
     830                 :             : 
     831                 :        1172 :     result = (TimestampTz) tp.tv_sec -
     832                 :             :         ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY);
     833                 :        1172 :     result = (result * USECS_PER_SEC) + tp.tv_usec;
     834                 :             : 
     835                 :        1172 :     return result;
     836                 :             : }
     837                 :             : 
     838                 :             : /*
     839                 :             :  * Frontend version of TimestampDifference(), since we are not linked with
     840                 :             :  * backend code.
     841                 :             :  */
     842                 :             : void
     843                 :         724 : feTimestampDifference(TimestampTz start_time, TimestampTz stop_time,
     844                 :             :                       long *secs, int *microsecs)
     845                 :             : {
     846                 :         724 :     TimestampTz diff = stop_time - start_time;
     847                 :             : 
     848         [ -  + ]:         724 :     if (diff <= 0)
     849                 :             :     {
     850                 :           0 :         *secs = 0;
     851                 :           0 :         *microsecs = 0;
     852                 :             :     }
     853                 :             :     else
     854                 :             :     {
     855                 :         724 :         *secs = (long) (diff / USECS_PER_SEC);
     856                 :         724 :         *microsecs = (int) (diff % USECS_PER_SEC);
     857                 :             :     }
     858                 :         724 : }
     859                 :             : 
     860                 :             : /*
     861                 :             :  * Frontend version of TimestampDifferenceExceeds(), since we are not
     862                 :             :  * linked with backend code.
     863                 :             :  */
     864                 :             : bool
     865                 :        1523 : feTimestampDifferenceExceeds(TimestampTz start_time,
     866                 :             :                              TimestampTz stop_time,
     867                 :             :                              int msec)
     868                 :             : {
     869                 :        1523 :     TimestampTz diff = stop_time - start_time;
     870                 :             : 
     871                 :        1523 :     return (diff >= msec * INT64CONST(1000));
     872                 :             : }
     873                 :             : 
     874                 :             : /*
     875                 :             :  * Converts an int64 to network byte order.
     876                 :             :  */
     877                 :             : void
     878                 :         804 : fe_sendint64(int64 i, char *buf)
     879                 :             : {
     880                 :         804 :     uint64      n64 = pg_hton64(i);
     881                 :             : 
     882                 :         804 :     memcpy(buf, &n64, sizeof(n64));
     883                 :         804 : }
     884                 :             : 
     885                 :             : /*
     886                 :             :  * Converts an int64 from network byte order to native format.
     887                 :             :  */
     888                 :             : int64
     889                 :        1940 : fe_recvint64(char *buf)
     890                 :             : {
     891                 :             :     uint64      n64;
     892                 :             : 
     893                 :        1940 :     memcpy(&n64, buf, sizeof(n64));
     894                 :             : 
     895                 :        1940 :     return pg_ntoh64(n64);
     896                 :             : }
        

Generated by: LCOV version 2.0-1