LCOV - code coverage report
Current view: top level - src/bin/scripts - common.c (source / functions) Hit Total Coverage
Test: PostgreSQL 14devel Lines: 126 168 75.0 %
Date: 2020-11-27 12:05:55 Functions: 11 12 91.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  *  common.c
       4             :  *      Common support routines for bin/scripts/
       5             :  *
       6             :  *
       7             :  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
       8             :  * Portions Copyright (c) 1994, Regents of the University of California
       9             :  *
      10             :  * src/bin/scripts/common.c
      11             :  *
      12             :  *-------------------------------------------------------------------------
      13             :  */
      14             : 
      15             : #include "postgres_fe.h"
      16             : 
      17             : #include <signal.h>
      18             : #include <unistd.h>
      19             : 
      20             : #include "common.h"
      21             : #include "common/connect.h"
      22             : #include "common/logging.h"
      23             : #include "common/string.h"
      24             : #include "fe_utils/cancel.h"
      25             : #include "fe_utils/string_utils.h"
      26             : 
      27             : #define ERRCODE_UNDEFINED_TABLE  "42P01"
      28             : 
      29             : /*
      30             :  * Provide strictly harmonized handling of --help and --version
      31             :  * options.
      32             :  */
      33             : void
      34         256 : handle_help_version_opts(int argc, char *argv[],
      35             :                          const char *fixed_progname, help_handler hlp)
      36             : {
      37         256 :     if (argc > 1)
      38             :     {
      39         252 :         if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
      40             :         {
      41          16 :             hlp(get_progname(argv[0]));
      42          16 :             exit(0);
      43             :         }
      44         236 :         if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
      45             :         {
      46          16 :             printf("%s (PostgreSQL) " PG_VERSION "\n", fixed_progname);
      47          16 :             exit(0);
      48             :         }
      49             :     }
      50         224 : }
      51             : 
      52             : 
      53             : /*
      54             :  * Make a database connection with the given parameters.
      55             :  *
      56             :  * An interactive password prompt is automatically issued if needed and
      57             :  * allowed by cparams->prompt_password.
      58             :  *
      59             :  * If allow_password_reuse is true, we will try to re-use any password
      60             :  * given during previous calls to this routine.  (Callers should not pass
      61             :  * allow_password_reuse=true unless reconnecting to the same database+user
      62             :  * as before, else we might create password exposure hazards.)
      63             :  */
      64             : PGconn *
      65         272 : connectDatabase(const ConnParams *cparams, const char *progname,
      66             :                 bool echo, bool fail_ok, bool allow_password_reuse)
      67             : {
      68             :     PGconn     *conn;
      69             :     bool        new_pass;
      70             :     static char *password = NULL;
      71             : 
      72             :     /* Callers must supply at least dbname; other params can be NULL */
      73             :     Assert(cparams->dbname);
      74             : 
      75         272 :     if (!allow_password_reuse && password)
      76             :     {
      77           0 :         free(password);
      78           0 :         password = NULL;
      79             :     }
      80             : 
      81         272 :     if (cparams->prompt_password == TRI_YES && password == NULL)
      82           0 :         password = simple_prompt("Password: ", false);
      83             : 
      84             :     /*
      85             :      * Start the connection.  Loop until we have a password if requested by
      86             :      * backend.
      87             :      */
      88             :     do
      89             :     {
      90             :         const char *keywords[8];
      91             :         const char *values[8];
      92         272 :         int         i = 0;
      93             : 
      94             :         /*
      95             :          * If dbname is a connstring, its entries can override the other
      96             :          * values obtained from cparams; but in turn, override_dbname can
      97             :          * override the dbname component of it.
      98             :          */
      99         272 :         keywords[i] = "host";
     100         272 :         values[i++] = cparams->pghost;
     101         272 :         keywords[i] = "port";
     102         272 :         values[i++] = cparams->pgport;
     103         272 :         keywords[i] = "user";
     104         272 :         values[i++] = cparams->pguser;
     105         272 :         keywords[i] = "password";
     106         272 :         values[i++] = password;
     107         272 :         keywords[i] = "dbname";
     108         272 :         values[i++] = cparams->dbname;
     109         272 :         if (cparams->override_dbname)
     110             :         {
     111          74 :             keywords[i] = "dbname";
     112          74 :             values[i++] = cparams->override_dbname;
     113             :         }
     114         272 :         keywords[i] = "fallback_application_name";
     115         272 :         values[i++] = progname;
     116         272 :         keywords[i] = NULL;
     117         272 :         values[i++] = NULL;
     118             :         Assert(i <= lengthof(keywords));
     119             : 
     120         272 :         new_pass = false;
     121         272 :         conn = PQconnectdbParams(keywords, values, true);
     122             : 
     123         272 :         if (!conn)
     124             :         {
     125           0 :             pg_log_error("could not connect to database %s: out of memory",
     126             :                          cparams->dbname);
     127           0 :             exit(1);
     128             :         }
     129             : 
     130             :         /*
     131             :          * No luck?  Trying asking (again) for a password.
     132             :          */
     133         272 :         if (PQstatus(conn) == CONNECTION_BAD &&
     134           0 :             PQconnectionNeedsPassword(conn) &&
     135           0 :             cparams->prompt_password != TRI_NO)
     136             :         {
     137           0 :             PQfinish(conn);
     138           0 :             if (password)
     139           0 :                 free(password);
     140           0 :             password = simple_prompt("Password: ", false);
     141           0 :             new_pass = true;
     142             :         }
     143         272 :     } while (new_pass);
     144             : 
     145             :     /* check to see that the backend connection was successfully made */
     146         272 :     if (PQstatus(conn) == CONNECTION_BAD)
     147             :     {
     148           0 :         if (fail_ok)
     149             :         {
     150           0 :             PQfinish(conn);
     151           0 :             return NULL;
     152             :         }
     153           0 :         pg_log_error("could not connect to database %s: %s",
     154             :                      cparams->dbname, PQerrorMessage(conn));
     155           0 :         exit(1);
     156             :     }
     157             : 
     158             :     /* Start strict; callers may override this. */
     159         272 :     PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
     160             : 
     161         272 :     return conn;
     162             : }
     163             : 
     164             : /*
     165             :  * Try to connect to the appropriate maintenance database.
     166             :  *
     167             :  * This differs from connectDatabase only in that it has a rule for
     168             :  * inserting a default "dbname" if none was given (which is why cparams
     169             :  * is not const).  Note that cparams->dbname should typically come from
     170             :  * a --maintenance-db command line parameter.
     171             :  */
     172             : PGconn *
     173          92 : connectMaintenanceDatabase(ConnParams *cparams,
     174             :                            const char *progname, bool echo)
     175             : {
     176             :     PGconn     *conn;
     177             : 
     178             :     /* If a maintenance database name was specified, just connect to it. */
     179          92 :     if (cparams->dbname)
     180           0 :         return connectDatabase(cparams, progname, echo, false, false);
     181             : 
     182             :     /* Otherwise, try postgres first and then template1. */
     183          92 :     cparams->dbname = "postgres";
     184          92 :     conn = connectDatabase(cparams, progname, echo, true, false);
     185          92 :     if (!conn)
     186             :     {
     187           0 :         cparams->dbname = "template1";
     188           0 :         conn = connectDatabase(cparams, progname, echo, false, false);
     189             :     }
     190          92 :     return conn;
     191             : }
     192             : 
     193             : /*
     194             :  * Disconnect the given connection, canceling any statement if one is active.
     195             :  */
     196             : void
     197         146 : disconnectDatabase(PGconn *conn)
     198             : {
     199             :     char        errbuf[256];
     200             : 
     201             :     Assert(conn != NULL);
     202             : 
     203         146 :     if (PQtransactionStatus(conn) == PQTRANS_ACTIVE)
     204             :     {
     205             :         PGcancel   *cancel;
     206             : 
     207           0 :         if ((cancel = PQgetCancel(conn)))
     208             :         {
     209           0 :             (void) PQcancel(cancel, errbuf, sizeof(errbuf));
     210           0 :             PQfreeCancel(cancel);
     211             :         }
     212             :     }
     213             : 
     214         146 :     PQfinish(conn);
     215         146 : }
     216             : 
     217             : /*
     218             :  * Run a query, return the results, exit program on failure.
     219             :  */
     220             : PGresult *
     221         574 : executeQuery(PGconn *conn, const char *query, bool echo)
     222             : {
     223             :     PGresult   *res;
     224             : 
     225         574 :     if (echo)
     226          94 :         printf("%s\n", query);
     227             : 
     228         574 :     res = PQexec(conn, query);
     229        1148 :     if (!res ||
     230         574 :         PQresultStatus(res) != PGRES_TUPLES_OK)
     231             :     {
     232           4 :         pg_log_error("query failed: %s", PQerrorMessage(conn));
     233           4 :         pg_log_info("query was: %s", query);
     234           4 :         PQfinish(conn);
     235           4 :         exit(1);
     236             :     }
     237             : 
     238         570 :     return res;
     239             : }
     240             : 
     241             : 
     242             : /*
     243             :  * As above for a SQL command (which returns nothing).
     244             :  */
     245             : void
     246         158 : executeCommand(PGconn *conn, const char *query, bool echo)
     247             : {
     248             :     PGresult   *res;
     249             : 
     250         158 :     if (echo)
     251          16 :         printf("%s\n", query);
     252             : 
     253         158 :     res = PQexec(conn, query);
     254         316 :     if (!res ||
     255         158 :         PQresultStatus(res) != PGRES_COMMAND_OK)
     256             :     {
     257           0 :         pg_log_error("query failed: %s", PQerrorMessage(conn));
     258           0 :         pg_log_info("query was: %s", query);
     259           0 :         PQfinish(conn);
     260           0 :         exit(1);
     261             :     }
     262             : 
     263         158 :     PQclear(res);
     264         158 : }
     265             : 
     266             : 
     267             : /*
     268             :  * As above for a SQL maintenance command (returns command success).
     269             :  * Command is executed with a cancel handler set, so Ctrl-C can
     270             :  * interrupt it.
     271             :  */
     272             : bool
     273          26 : executeMaintenanceCommand(PGconn *conn, const char *query, bool echo)
     274             : {
     275             :     PGresult   *res;
     276             :     bool        r;
     277             : 
     278          26 :     if (echo)
     279          16 :         printf("%s\n", query);
     280             : 
     281          26 :     SetCancelConn(conn);
     282          26 :     res = PQexec(conn, query);
     283          26 :     ResetCancelConn();
     284             : 
     285          26 :     r = (res && PQresultStatus(res) == PGRES_COMMAND_OK);
     286             : 
     287          26 :     if (res)
     288          26 :         PQclear(res);
     289             : 
     290          26 :     return r;
     291             : }
     292             : 
     293             : /*
     294             :  * Consume all the results generated for the given connection until
     295             :  * nothing remains.  If at least one error is encountered, return false.
     296             :  * Note that this will block if the connection is busy.
     297             :  */
     298             : bool
     299         146 : consumeQueryResult(PGconn *conn)
     300             : {
     301         146 :     bool        ok = true;
     302             :     PGresult   *result;
     303             : 
     304         146 :     SetCancelConn(conn);
     305         294 :     while ((result = PQgetResult(conn)) != NULL)
     306             :     {
     307         148 :         if (!processQueryResult(conn, result))
     308           6 :             ok = false;
     309             :     }
     310         146 :     ResetCancelConn();
     311         146 :     return ok;
     312             : }
     313             : 
     314             : /*
     315             :  * Process (and delete) a query result.  Returns true if there's no error,
     316             :  * false otherwise -- but errors about trying to work on a missing relation
     317             :  * are reported and subsequently ignored.
     318             :  */
     319             : bool
     320        4586 : processQueryResult(PGconn *conn, PGresult *result)
     321             : {
     322             :     /*
     323             :      * If it's an error, report it.  Errors about a missing table are harmless
     324             :      * so we continue processing; but die for other errors.
     325             :      */
     326        4586 :     if (PQresultStatus(result) != PGRES_COMMAND_OK)
     327             :     {
     328           6 :         char       *sqlState = PQresultErrorField(result, PG_DIAG_SQLSTATE);
     329             : 
     330           6 :         pg_log_error("processing of database \"%s\" failed: %s",
     331             :                      PQdb(conn), PQerrorMessage(conn));
     332             : 
     333           6 :         if (sqlState && strcmp(sqlState, ERRCODE_UNDEFINED_TABLE) != 0)
     334             :         {
     335           6 :             PQclear(result);
     336           6 :             return false;
     337             :         }
     338             :     }
     339             : 
     340        4580 :     PQclear(result);
     341        4580 :     return true;
     342             : }
     343             : 
     344             : 
     345             : /*
     346             :  * Split TABLE[(COLUMNS)] into TABLE and [(COLUMNS)] portions.  When you
     347             :  * finish using them, pg_free(*table).  *columns is a pointer into "spec",
     348             :  * possibly to its NUL terminator.
     349             :  */
     350             : void
     351          70 : splitTableColumnsSpec(const char *spec, int encoding,
     352             :                       char **table, const char **columns)
     353             : {
     354          70 :     bool        inquotes = false;
     355          70 :     const char *cp = spec;
     356             : 
     357             :     /*
     358             :      * Find the first '(' not identifier-quoted.  Based on
     359             :      * dequote_downcase_identifier().
     360             :      */
     361         962 :     while (*cp && (*cp != '(' || inquotes))
     362             :     {
     363         892 :         if (*cp == '"')
     364             :         {
     365           6 :             if (inquotes && cp[1] == '"')
     366           2 :                 cp++;           /* pair does not affect quoting */
     367             :             else
     368           4 :                 inquotes = !inquotes;
     369           6 :             cp++;
     370             :         }
     371             :         else
     372         886 :             cp += PQmblen(cp, encoding);
     373             :     }
     374          70 :     *table = pnstrdup(spec, cp - spec);
     375          70 :     *columns = cp;
     376          70 : }
     377             : 
     378             : /*
     379             :  * Break apart TABLE[(COLUMNS)] of "spec".  With the reset_val of search_path
     380             :  * in effect, have regclassin() interpret the TABLE portion.  Append to "buf"
     381             :  * the qualified name of TABLE, followed by any (COLUMNS).  Exit on failure.
     382             :  * We use this to interpret --table=foo under the search path psql would get,
     383             :  * in advance of "ANALYZE public.foo" under the always-secure search path.
     384             :  */
     385             : void
     386          50 : appendQualifiedRelation(PQExpBuffer buf, const char *spec,
     387             :                         PGconn *conn, bool echo)
     388             : {
     389             :     char       *table;
     390             :     const char *columns;
     391             :     PQExpBufferData sql;
     392             :     PGresult   *res;
     393             :     int         ntups;
     394             : 
     395          50 :     splitTableColumnsSpec(spec, PQclientEncoding(conn), &table, &columns);
     396             : 
     397             :     /*
     398             :      * Query must remain ABSOLUTELY devoid of unqualified names.  This would
     399             :      * be unnecessary given a regclassin() variant taking a search_path
     400             :      * argument.
     401             :      */
     402          50 :     initPQExpBuffer(&sql);
     403          50 :     appendPQExpBufferStr(&sql,
     404             :                          "SELECT c.relname, ns.nspname\n"
     405             :                          " FROM pg_catalog.pg_class c,"
     406             :                          " pg_catalog.pg_namespace ns\n"
     407             :                          " WHERE c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
     408             :                          "  AND c.oid OPERATOR(pg_catalog.=) ");
     409          50 :     appendStringLiteralConn(&sql, table, conn);
     410          50 :     appendPQExpBufferStr(&sql, "::pg_catalog.regclass;");
     411             : 
     412          50 :     executeCommand(conn, "RESET search_path;", echo);
     413             : 
     414             :     /*
     415             :      * One row is a typical result, as is a nonexistent relation ERROR.
     416             :      * regclassin() unconditionally accepts all-digits input as an OID; if no
     417             :      * relation has that OID; this query returns no rows.  Catalog corruption
     418             :      * might elicit other row counts.
     419             :      */
     420          50 :     res = executeQuery(conn, sql.data, echo);
     421          48 :     ntups = PQntuples(res);
     422          48 :     if (ntups != 1)
     423             :     {
     424           0 :         pg_log_error(ngettext("query returned %d row instead of one: %s",
     425             :                               "query returned %d rows instead of one: %s",
     426             :                               ntups),
     427             :                      ntups, sql.data);
     428           0 :         PQfinish(conn);
     429           0 :         exit(1);
     430             :     }
     431          48 :     appendPQExpBufferStr(buf,
     432          48 :                          fmtQualifiedId(PQgetvalue(res, 0, 1),
     433          48 :                                         PQgetvalue(res, 0, 0)));
     434          48 :     appendPQExpBufferStr(buf, columns);
     435          48 :     PQclear(res);
     436          48 :     termPQExpBuffer(&sql);
     437          48 :     pg_free(table);
     438             : 
     439          48 :     PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
     440          48 : }
     441             : 
     442             : 
     443             : /*
     444             :  * Check yes/no answer in a localized way.  1=yes, 0=no, -1=neither.
     445             :  */
     446             : 
     447             : /* translator: abbreviation for "yes" */
     448             : #define PG_YESLETTER gettext_noop("y")
     449             : /* translator: abbreviation for "no" */
     450             : #define PG_NOLETTER gettext_noop("n")
     451             : 
     452             : bool
     453           0 : yesno_prompt(const char *question)
     454             : {
     455             :     char        prompt[256];
     456             : 
     457             :     /*------
     458             :        translator: This is a question followed by the translated options for
     459             :        "yes" and "no". */
     460           0 :     snprintf(prompt, sizeof(prompt), _("%s (%s/%s) "),
     461             :              _(question), _(PG_YESLETTER), _(PG_NOLETTER));
     462             : 
     463             :     for (;;)
     464           0 :     {
     465             :         char       *resp;
     466             : 
     467           0 :         resp = simple_prompt(prompt, true);
     468             : 
     469           0 :         if (strcmp(resp, _(PG_YESLETTER)) == 0)
     470             :         {
     471           0 :             free(resp);
     472           0 :             return true;
     473             :         }
     474           0 :         if (strcmp(resp, _(PG_NOLETTER)) == 0)
     475             :         {
     476           0 :             free(resp);
     477           0 :             return false;
     478             :         }
     479           0 :         free(resp);
     480             : 
     481           0 :         printf(_("Please answer \"%s\" or \"%s\".\n"),
     482             :                _(PG_YESLETTER), _(PG_NOLETTER));
     483             :     }
     484             : }

Generated by: LCOV version 1.13