| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * common.c |
| 4 | * Common support routines for bin/scripts/ |
| 5 | * |
| 6 | * |
| 7 | * Portions Copyright (c) 1996-2019, 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/logging.h" |
| 22 | #include "fe_utils/connect.h" |
| 23 | #include "fe_utils/string_utils.h" |
| 24 | |
| 25 | |
| 26 | static PGcancel *volatile cancelConn = NULL; |
| 27 | bool CancelRequested = false; |
| 28 | |
| 29 | #ifdef WIN32 |
| 30 | static CRITICAL_SECTION cancelConnLock; |
| 31 | #endif |
| 32 | |
| 33 | /* |
| 34 | * Provide strictly harmonized handling of --help and --version |
| 35 | * options. |
| 36 | */ |
| 37 | void |
| 38 | handle_help_version_opts(int argc, char *argv[], |
| 39 | const char *fixed_progname, help_handler hlp) |
| 40 | { |
| 41 | if (argc > 1) |
| 42 | { |
| 43 | if (strcmp(argv[1], "--help" ) == 0 || strcmp(argv[1], "-?" ) == 0) |
| 44 | { |
| 45 | hlp(get_progname(argv[0])); |
| 46 | exit(0); |
| 47 | } |
| 48 | if (strcmp(argv[1], "--version" ) == 0 || strcmp(argv[1], "-V" ) == 0) |
| 49 | { |
| 50 | printf("%s (PostgreSQL) " PG_VERSION "\n" , fixed_progname); |
| 51 | exit(0); |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | |
| 57 | /* |
| 58 | * Make a database connection with the given parameters. |
| 59 | * |
| 60 | * An interactive password prompt is automatically issued if needed and |
| 61 | * allowed by prompt_password. |
| 62 | * |
| 63 | * If allow_password_reuse is true, we will try to re-use any password |
| 64 | * given during previous calls to this routine. (Callers should not pass |
| 65 | * allow_password_reuse=true unless reconnecting to the same database+user |
| 66 | * as before, else we might create password exposure hazards.) |
| 67 | */ |
| 68 | PGconn * |
| 69 | connectDatabase(const char *dbname, const char *pghost, |
| 70 | const char *pgport, const char *pguser, |
| 71 | enum trivalue prompt_password, const char *progname, |
| 72 | bool echo, bool fail_ok, bool allow_password_reuse) |
| 73 | { |
| 74 | PGconn *conn; |
| 75 | bool new_pass; |
| 76 | static bool have_password = false; |
| 77 | static char password[100]; |
| 78 | |
| 79 | if (!allow_password_reuse) |
| 80 | have_password = false; |
| 81 | |
| 82 | if (!have_password && prompt_password == TRI_YES) |
| 83 | { |
| 84 | simple_prompt("Password: " , password, sizeof(password), false); |
| 85 | have_password = true; |
| 86 | } |
| 87 | |
| 88 | /* |
| 89 | * Start the connection. Loop until we have a password if requested by |
| 90 | * backend. |
| 91 | */ |
| 92 | do |
| 93 | { |
| 94 | const char *keywords[7]; |
| 95 | const char *values[7]; |
| 96 | |
| 97 | keywords[0] = "host" ; |
| 98 | values[0] = pghost; |
| 99 | keywords[1] = "port" ; |
| 100 | values[1] = pgport; |
| 101 | keywords[2] = "user" ; |
| 102 | values[2] = pguser; |
| 103 | keywords[3] = "password" ; |
| 104 | values[3] = have_password ? password : NULL; |
| 105 | keywords[4] = "dbname" ; |
| 106 | values[4] = dbname; |
| 107 | keywords[5] = "fallback_application_name" ; |
| 108 | values[5] = progname; |
| 109 | keywords[6] = NULL; |
| 110 | values[6] = NULL; |
| 111 | |
| 112 | new_pass = false; |
| 113 | conn = PQconnectdbParams(keywords, values, true); |
| 114 | |
| 115 | if (!conn) |
| 116 | { |
| 117 | pg_log_error("could not connect to database %s: out of memory" , |
| 118 | dbname); |
| 119 | exit(1); |
| 120 | } |
| 121 | |
| 122 | /* |
| 123 | * No luck? Trying asking (again) for a password. |
| 124 | */ |
| 125 | if (PQstatus(conn) == CONNECTION_BAD && |
| 126 | PQconnectionNeedsPassword(conn) && |
| 127 | prompt_password != TRI_NO) |
| 128 | { |
| 129 | PQfinish(conn); |
| 130 | simple_prompt("Password: " , password, sizeof(password), false); |
| 131 | have_password = true; |
| 132 | new_pass = true; |
| 133 | } |
| 134 | } while (new_pass); |
| 135 | |
| 136 | /* check to see that the backend connection was successfully made */ |
| 137 | if (PQstatus(conn) == CONNECTION_BAD) |
| 138 | { |
| 139 | if (fail_ok) |
| 140 | { |
| 141 | PQfinish(conn); |
| 142 | return NULL; |
| 143 | } |
| 144 | pg_log_error("could not connect to database %s: %s" , |
| 145 | dbname, PQerrorMessage(conn)); |
| 146 | exit(1); |
| 147 | } |
| 148 | |
| 149 | PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, |
| 150 | progname, echo)); |
| 151 | |
| 152 | return conn; |
| 153 | } |
| 154 | |
| 155 | /* |
| 156 | * Try to connect to the appropriate maintenance database. |
| 157 | */ |
| 158 | PGconn * |
| 159 | connectMaintenanceDatabase(const char *maintenance_db, |
| 160 | const char *pghost, const char *pgport, |
| 161 | const char *pguser, enum trivalue prompt_password, |
| 162 | const char *progname, bool echo) |
| 163 | { |
| 164 | PGconn *conn; |
| 165 | |
| 166 | /* If a maintenance database name was specified, just connect to it. */ |
| 167 | if (maintenance_db) |
| 168 | return connectDatabase(maintenance_db, pghost, pgport, pguser, |
| 169 | prompt_password, progname, echo, false, false); |
| 170 | |
| 171 | /* Otherwise, try postgres first and then template1. */ |
| 172 | conn = connectDatabase("postgres" , pghost, pgport, pguser, prompt_password, |
| 173 | progname, echo, true, false); |
| 174 | if (!conn) |
| 175 | conn = connectDatabase("template1" , pghost, pgport, pguser, |
| 176 | prompt_password, progname, echo, false, false); |
| 177 | |
| 178 | return conn; |
| 179 | } |
| 180 | |
| 181 | /* |
| 182 | * Run a query, return the results, exit program on failure. |
| 183 | */ |
| 184 | PGresult * |
| 185 | executeQuery(PGconn *conn, const char *query, const char *progname, bool echo) |
| 186 | { |
| 187 | PGresult *res; |
| 188 | |
| 189 | if (echo) |
| 190 | printf("%s\n" , query); |
| 191 | |
| 192 | res = PQexec(conn, query); |
| 193 | if (!res || |
| 194 | PQresultStatus(res) != PGRES_TUPLES_OK) |
| 195 | { |
| 196 | pg_log_error("query failed: %s" , PQerrorMessage(conn)); |
| 197 | pg_log_info("query was: %s" , query); |
| 198 | PQfinish(conn); |
| 199 | exit(1); |
| 200 | } |
| 201 | |
| 202 | return res; |
| 203 | } |
| 204 | |
| 205 | |
| 206 | /* |
| 207 | * As above for a SQL command (which returns nothing). |
| 208 | */ |
| 209 | void |
| 210 | executeCommand(PGconn *conn, const char *query, |
| 211 | const char *progname, bool echo) |
| 212 | { |
| 213 | PGresult *res; |
| 214 | |
| 215 | if (echo) |
| 216 | printf("%s\n" , query); |
| 217 | |
| 218 | res = PQexec(conn, query); |
| 219 | if (!res || |
| 220 | PQresultStatus(res) != PGRES_COMMAND_OK) |
| 221 | { |
| 222 | pg_log_error("query failed: %s" , PQerrorMessage(conn)); |
| 223 | pg_log_info("query was: %s" , query); |
| 224 | PQfinish(conn); |
| 225 | exit(1); |
| 226 | } |
| 227 | |
| 228 | PQclear(res); |
| 229 | } |
| 230 | |
| 231 | |
| 232 | /* |
| 233 | * As above for a SQL maintenance command (returns command success). |
| 234 | * Command is executed with a cancel handler set, so Ctrl-C can |
| 235 | * interrupt it. |
| 236 | */ |
| 237 | bool |
| 238 | executeMaintenanceCommand(PGconn *conn, const char *query, bool echo) |
| 239 | { |
| 240 | PGresult *res; |
| 241 | bool r; |
| 242 | |
| 243 | if (echo) |
| 244 | printf("%s\n" , query); |
| 245 | |
| 246 | SetCancelConn(conn); |
| 247 | res = PQexec(conn, query); |
| 248 | ResetCancelConn(); |
| 249 | |
| 250 | r = (res && PQresultStatus(res) == PGRES_COMMAND_OK); |
| 251 | |
| 252 | if (res) |
| 253 | PQclear(res); |
| 254 | |
| 255 | return r; |
| 256 | } |
| 257 | |
| 258 | |
| 259 | /* |
| 260 | * Split TABLE[(COLUMNS)] into TABLE and [(COLUMNS)] portions. When you |
| 261 | * finish using them, pg_free(*table). *columns is a pointer into "spec", |
| 262 | * possibly to its NUL terminator. |
| 263 | */ |
| 264 | void |
| 265 | splitTableColumnsSpec(const char *spec, int encoding, |
| 266 | char **table, const char **columns) |
| 267 | { |
| 268 | bool inquotes = false; |
| 269 | const char *cp = spec; |
| 270 | |
| 271 | /* |
| 272 | * Find the first '(' not identifier-quoted. Based on |
| 273 | * dequote_downcase_identifier(). |
| 274 | */ |
| 275 | while (*cp && (*cp != '(' || inquotes)) |
| 276 | { |
| 277 | if (*cp == '"') |
| 278 | { |
| 279 | if (inquotes && cp[1] == '"') |
| 280 | cp++; /* pair does not affect quoting */ |
| 281 | else |
| 282 | inquotes = !inquotes; |
| 283 | cp++; |
| 284 | } |
| 285 | else |
| 286 | cp += PQmblen(cp, encoding); |
| 287 | } |
| 288 | *table = pg_strdup(spec); |
| 289 | (*table)[cp - spec] = '\0'; /* no strndup */ |
| 290 | *columns = cp; |
| 291 | } |
| 292 | |
| 293 | /* |
| 294 | * Break apart TABLE[(COLUMNS)] of "spec". With the reset_val of search_path |
| 295 | * in effect, have regclassin() interpret the TABLE portion. Append to "buf" |
| 296 | * the qualified name of TABLE, followed by any (COLUMNS). Exit on failure. |
| 297 | * We use this to interpret --table=foo under the search path psql would get, |
| 298 | * in advance of "ANALYZE public.foo" under the always-secure search path. |
| 299 | */ |
| 300 | void |
| 301 | appendQualifiedRelation(PQExpBuffer buf, const char *spec, |
| 302 | PGconn *conn, const char *progname, bool echo) |
| 303 | { |
| 304 | char *table; |
| 305 | const char *columns; |
| 306 | PQExpBufferData sql; |
| 307 | PGresult *res; |
| 308 | int ntups; |
| 309 | |
| 310 | splitTableColumnsSpec(spec, PQclientEncoding(conn), &table, &columns); |
| 311 | |
| 312 | /* |
| 313 | * Query must remain ABSOLUTELY devoid of unqualified names. This would |
| 314 | * be unnecessary given a regclassin() variant taking a search_path |
| 315 | * argument. |
| 316 | */ |
| 317 | initPQExpBuffer(&sql); |
| 318 | appendPQExpBufferStr(&sql, |
| 319 | "SELECT c.relname, ns.nspname\n" |
| 320 | " FROM pg_catalog.pg_class c," |
| 321 | " pg_catalog.pg_namespace ns\n" |
| 322 | " WHERE c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n" |
| 323 | " AND c.oid OPERATOR(pg_catalog.=) " ); |
| 324 | appendStringLiteralConn(&sql, table, conn); |
| 325 | appendPQExpBufferStr(&sql, "::pg_catalog.regclass;" ); |
| 326 | |
| 327 | executeCommand(conn, "RESET search_path;" , progname, echo); |
| 328 | |
| 329 | /* |
| 330 | * One row is a typical result, as is a nonexistent relation ERROR. |
| 331 | * regclassin() unconditionally accepts all-digits input as an OID; if no |
| 332 | * relation has that OID; this query returns no rows. Catalog corruption |
| 333 | * might elicit other row counts. |
| 334 | */ |
| 335 | res = executeQuery(conn, sql.data, progname, echo); |
| 336 | ntups = PQntuples(res); |
| 337 | if (ntups != 1) |
| 338 | { |
| 339 | pg_log_error(ngettext("query returned %d row instead of one: %s" , |
| 340 | "query returned %d rows instead of one: %s" , |
| 341 | ntups), |
| 342 | ntups, sql.data); |
| 343 | PQfinish(conn); |
| 344 | exit(1); |
| 345 | } |
| 346 | appendPQExpBufferStr(buf, |
| 347 | fmtQualifiedId(PQgetvalue(res, 0, 1), |
| 348 | PQgetvalue(res, 0, 0))); |
| 349 | appendPQExpBufferStr(buf, columns); |
| 350 | PQclear(res); |
| 351 | termPQExpBuffer(&sql); |
| 352 | pg_free(table); |
| 353 | |
| 354 | PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, |
| 355 | progname, echo)); |
| 356 | } |
| 357 | |
| 358 | |
| 359 | /* |
| 360 | * Check yes/no answer in a localized way. 1=yes, 0=no, -1=neither. |
| 361 | */ |
| 362 | |
| 363 | /* translator: abbreviation for "yes" */ |
| 364 | #define PG_YESLETTER gettext_noop("y") |
| 365 | /* translator: abbreviation for "no" */ |
| 366 | #define PG_NOLETTER gettext_noop("n") |
| 367 | |
| 368 | bool |
| 369 | yesno_prompt(const char *question) |
| 370 | { |
| 371 | char prompt[256]; |
| 372 | |
| 373 | /*------ |
| 374 | translator: This is a question followed by the translated options for |
| 375 | "yes" and "no". */ |
| 376 | snprintf(prompt, sizeof(prompt), _("%s (%s/%s) " ), |
| 377 | _(question), _(PG_YESLETTER), _(PG_NOLETTER)); |
| 378 | |
| 379 | for (;;) |
| 380 | { |
| 381 | char resp[10]; |
| 382 | |
| 383 | simple_prompt(prompt, resp, sizeof(resp), true); |
| 384 | |
| 385 | if (strcmp(resp, _(PG_YESLETTER)) == 0) |
| 386 | return true; |
| 387 | if (strcmp(resp, _(PG_NOLETTER)) == 0) |
| 388 | return false; |
| 389 | |
| 390 | printf(_("Please answer \"%s\" or \"%s\".\n" ), |
| 391 | _(PG_YESLETTER), _(PG_NOLETTER)); |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | /* |
| 396 | * SetCancelConn |
| 397 | * |
| 398 | * Set cancelConn to point to the current database connection. |
| 399 | */ |
| 400 | void |
| 401 | SetCancelConn(PGconn *conn) |
| 402 | { |
| 403 | PGcancel *oldCancelConn; |
| 404 | |
| 405 | #ifdef WIN32 |
| 406 | EnterCriticalSection(&cancelConnLock); |
| 407 | #endif |
| 408 | |
| 409 | /* Free the old one if we have one */ |
| 410 | oldCancelConn = cancelConn; |
| 411 | |
| 412 | /* be sure handle_sigint doesn't use pointer while freeing */ |
| 413 | cancelConn = NULL; |
| 414 | |
| 415 | if (oldCancelConn != NULL) |
| 416 | PQfreeCancel(oldCancelConn); |
| 417 | |
| 418 | cancelConn = PQgetCancel(conn); |
| 419 | |
| 420 | #ifdef WIN32 |
| 421 | LeaveCriticalSection(&cancelConnLock); |
| 422 | #endif |
| 423 | } |
| 424 | |
| 425 | /* |
| 426 | * ResetCancelConn |
| 427 | * |
| 428 | * Free the current cancel connection, if any, and set to NULL. |
| 429 | */ |
| 430 | void |
| 431 | ResetCancelConn(void) |
| 432 | { |
| 433 | PGcancel *oldCancelConn; |
| 434 | |
| 435 | #ifdef WIN32 |
| 436 | EnterCriticalSection(&cancelConnLock); |
| 437 | #endif |
| 438 | |
| 439 | oldCancelConn = cancelConn; |
| 440 | |
| 441 | /* be sure handle_sigint doesn't use pointer while freeing */ |
| 442 | cancelConn = NULL; |
| 443 | |
| 444 | if (oldCancelConn != NULL) |
| 445 | PQfreeCancel(oldCancelConn); |
| 446 | |
| 447 | #ifdef WIN32 |
| 448 | LeaveCriticalSection(&cancelConnLock); |
| 449 | #endif |
| 450 | } |
| 451 | |
| 452 | #ifndef WIN32 |
| 453 | /* |
| 454 | * Handle interrupt signals by canceling the current command, if a cancelConn |
| 455 | * is set. |
| 456 | */ |
| 457 | static void |
| 458 | handle_sigint(SIGNAL_ARGS) |
| 459 | { |
| 460 | int save_errno = errno; |
| 461 | char errbuf[256]; |
| 462 | |
| 463 | /* Send QueryCancel if we are processing a database query */ |
| 464 | if (cancelConn != NULL) |
| 465 | { |
| 466 | if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) |
| 467 | { |
| 468 | CancelRequested = true; |
| 469 | fprintf(stderr, _("Cancel request sent\n" )); |
| 470 | } |
| 471 | else |
| 472 | fprintf(stderr, _("Could not send cancel request: %s" ), errbuf); |
| 473 | } |
| 474 | else |
| 475 | CancelRequested = true; |
| 476 | |
| 477 | errno = save_errno; /* just in case the write changed it */ |
| 478 | } |
| 479 | |
| 480 | void |
| 481 | setup_cancel_handler(void) |
| 482 | { |
| 483 | pqsignal(SIGINT, handle_sigint); |
| 484 | } |
| 485 | #else /* WIN32 */ |
| 486 | |
| 487 | /* |
| 488 | * Console control handler for Win32. Note that the control handler will |
| 489 | * execute on a *different thread* than the main one, so we need to do |
| 490 | * proper locking around those structures. |
| 491 | */ |
| 492 | static BOOL WINAPI |
| 493 | consoleHandler(DWORD dwCtrlType) |
| 494 | { |
| 495 | char errbuf[256]; |
| 496 | |
| 497 | if (dwCtrlType == CTRL_C_EVENT || |
| 498 | dwCtrlType == CTRL_BREAK_EVENT) |
| 499 | { |
| 500 | /* Send QueryCancel if we are processing a database query */ |
| 501 | EnterCriticalSection(&cancelConnLock); |
| 502 | if (cancelConn != NULL) |
| 503 | { |
| 504 | if (PQcancel(cancelConn, errbuf, sizeof(errbuf))) |
| 505 | { |
| 506 | fprintf(stderr, _("Cancel request sent\n" )); |
| 507 | CancelRequested = true; |
| 508 | } |
| 509 | else |
| 510 | fprintf(stderr, _("Could not send cancel request: %s" ), errbuf); |
| 511 | } |
| 512 | else |
| 513 | CancelRequested = true; |
| 514 | |
| 515 | LeaveCriticalSection(&cancelConnLock); |
| 516 | |
| 517 | return TRUE; |
| 518 | } |
| 519 | else |
| 520 | /* Return FALSE for any signals not being handled */ |
| 521 | return FALSE; |
| 522 | } |
| 523 | |
| 524 | void |
| 525 | setup_cancel_handler(void) |
| 526 | { |
| 527 | InitializeCriticalSection(&cancelConnLock); |
| 528 | |
| 529 | SetConsoleCtrlHandler(consoleHandler, TRUE); |
| 530 | } |
| 531 | |
| 532 | #endif /* WIN32 */ |
| 533 | |