diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 95d7b3f98f..9a9b1e1d51 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -58,6 +58,20 @@ typedef enum _teSection SECTION_POST_DATA /* stuff to be processed after data */ } teSection; +/* Parameters needed by ConnectDatabase; same for dump and restore */ +typedef struct _connParams +{ + /* These fields record the actual command line parameters */ + char *dbname; /* this may be a connstring! */ + char *pgport; + char *pghost; + char *username; + trivalue promptPassword; + /* If not NULL, this overrides the dbname obtained from command line */ + /* (but *only* the DB name, not anything else in the connstring) */ + char *override_dbname; +} ConnParams; + typedef struct _restoreOptions { int createDB; /* Issue commands to create the database */ @@ -107,12 +121,9 @@ typedef struct _restoreOptions SimpleStringList tableNames; int useDB; - char *dbname; /* subject to expand_dbname */ - char *pgport; - char *pghost; - char *username; + ConnParams cparams; /* parameters to use if useDB */ + int noDataForFailedTables; - trivalue promptPassword; int exit_on_error; int compression; int suppressDumpWarnings; /* Suppress output of WARNING entries @@ -127,10 +138,8 @@ typedef struct _restoreOptions typedef struct _dumpOptions { - const char *dbname; /* subject to expand_dbname */ - const char *pghost; - const char *pgport; - const char *username; + ConnParams cparams; + bool oids; int binary_upgrade; @@ -248,12 +257,9 @@ typedef void (*SetupWorkerPtrType) (Archive *AH); * Main archiver interface. */ -extern void ConnectDatabase(Archive *AH, - const char *dbname, - const char *pghost, - const char *pgport, - const char *username, - trivalue prompt_password); +extern void ConnectDatabase(Archive *AHX, + const ConnParams *cparams, + bool isReconnect); extern void DisconnectDatabase(Archive *AHX); extern PGconn *GetConnection(Archive *AHX); diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index eab2f9e253..a7ad61ffa9 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -144,6 +144,7 @@ InitDumpOptions(DumpOptions *opts) memset(opts, 0, sizeof(DumpOptions)); /* set any fields that shouldn't default to zeroes */ opts->include_everything = true; + opts->cparams.promptPassword = TRI_DEFAULT; opts->dumpSections = DUMP_UNSECTIONED; } @@ -157,6 +158,11 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) DumpOptions *dopt = NewDumpOptions(); /* this is the inverse of what's at the end of pg_dump.c's main() */ + dopt->cparams.dbname = ropt->cparams.dbname ? pg_strdup(ropt->cparams.dbname) : NULL; + dopt->cparams.pgport = ropt->cparams.pgport ? pg_strdup(ropt->cparams.pgport) : NULL; + dopt->cparams.pghost = ropt->cparams.pghost ? pg_strdup(ropt->cparams.pghost) : NULL; + dopt->cparams.username = ropt->cparams.username ? pg_strdup(ropt->cparams.username) : NULL; + dopt->cparams.promptPassword = ropt->cparams.promptPassword; dopt->outputClean = ropt->dropSchema; dopt->dataOnly = ropt->dataOnly; dopt->schemaOnly = ropt->schemaOnly; @@ -401,9 +407,7 @@ RestoreArchive(Archive *AHX) AHX->minRemoteVersion = 0; AHX->maxRemoteVersion = 9999999; - ConnectDatabase(AHX, ropt->dbname, - ropt->pghost, ropt->pgport, ropt->username, - ropt->promptPassword); + ConnectDatabase(AHX, &ropt->cparams, false); /* * If we're talking to the DB directly, don't send comments since they @@ -819,16 +823,8 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel) if (strcmp(te->desc, "DATABASE") == 0 || strcmp(te->desc, "DATABASE PROPERTIES") == 0) { - PQExpBufferData connstr; - - initPQExpBuffer(&connstr); - appendPQExpBufferStr(&connstr, "dbname="); - appendConnStrVal(&connstr, te->tag); - /* Abandon struct, but keep its buffer until process exit. */ - ahlog(AH, 1, "connecting to new database \"%s\"\n", te->tag); _reconnectToDB(AH, te->tag); - ropt->dbname = connstr.data; } } @@ -960,7 +956,7 @@ NewRestoreOptions(void) /* set any fields that shouldn't default to zeroes */ opts->format = archUnknown; - opts->promptPassword = TRI_DEFAULT; + opts->cparams.promptPassword = TRI_DEFAULT; opts->dumpSections = DUMP_UNSECTIONED; return opts; @@ -2386,8 +2382,6 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, else AH->format = fmt; - AH->promptPassword = TRI_DEFAULT; - switch (AH->format) { case archCustom: @@ -3256,27 +3250,20 @@ _doSetWithOids(ArchiveHandle *AH, const bool withOids) * If we're currently restoring right into a database, this will * actually establish a connection. Otherwise it puts a \connect into * the script output. - * - * NULL dbname implies reconnecting to the current DB (pretty useless). */ static void _reconnectToDB(ArchiveHandle *AH, const char *dbname) { if (RestoringToDB(AH)) - ReconnectToServer(AH, dbname, NULL); + ReconnectToServer(AH, dbname); else { - if (dbname) - { - PQExpBufferData connectbuf; + PQExpBufferData connectbuf; - initPQExpBuffer(&connectbuf); - appendPsqlMetaConnect(&connectbuf, dbname); - ahprintf(AH, "%s\n", connectbuf.data); - termPQExpBuffer(&connectbuf); - } - else - ahprintf(AH, "%s\n", "\\connect -\n"); + initPQExpBuffer(&connectbuf); + appendPsqlMetaConnect(&connectbuf, dbname); + ahprintf(AH, "%s\n", connectbuf.data); + termPQExpBuffer(&connectbuf); } /* @@ -4186,9 +4173,7 @@ restore_toc_entries_postfork(ArchiveHandle *AH, TocEntry *pending_list) /* * Now reconnect the single parent connection. */ - ConnectDatabase((Archive *) AH, ropt->dbname, - ropt->pghost, ropt->pgport, ropt->username, - ropt->promptPassword); + ConnectDatabase((Archive *) AH, &ropt->cparams, true); /* re-establish fixed state */ _doSetFixedOutputState(AH); @@ -4777,54 +4762,15 @@ CloneArchive(ArchiveHandle *AH) clone->public.n_errors = 0; /* - * Connect our new clone object to the database: In parallel restore the - * parent is already disconnected, because we can connect the worker - * processes independently to the database (no snapshot sync required). In - * parallel backup we clone the parent's existing connection. + * Connect our new clone object to the database, using the same connection + * parameters used for the original connection. */ + ConnectDatabase((Archive *) clone, &clone->public.ropt->cparams, true); + + /* re-establish fixed state */ if (AH->mode == archModeRead) - { - RestoreOptions *ropt = AH->public.ropt; - - Assert(AH->connection == NULL); - - /* this also sets clone->connection */ - ConnectDatabase((Archive *) clone, ropt->dbname, - ropt->pghost, ropt->pgport, ropt->username, - ropt->promptPassword); - - /* re-establish fixed state */ _doSetFixedOutputState(clone); - } - else - { - PQExpBufferData connstr; - char *pghost; - char *pgport; - char *username; - - Assert(AH->connection != NULL); - - /* - * Even though we are technically accessing the parent's database - * object here, these functions are fine to be called like that - * because all just return a pointer and do not actually send/receive - * any data to/from the database. - */ - initPQExpBuffer(&connstr); - appendPQExpBuffer(&connstr, "dbname="); - appendConnStrVal(&connstr, PQdb(AH->connection)); - pghost = PQhost(AH->connection); - pgport = PQport(AH->connection); - username = PQuser(AH->connection); - - /* this also sets clone->connection */ - ConnectDatabase((Archive *) clone, connstr.data, - pghost, pgport, username, TRI_NO); - - termPQExpBuffer(&connstr); - /* setupDumpWorker will fix up connection state */ - } + /* in write case, setupDumpWorker will fix up connection state */ /* Let the format-specific code have a chance too */ clone->ClonePtr(clone); diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h index 237053d45f..6a6d740d7f 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -308,7 +308,6 @@ struct _archiveHandle /* Stuff for direct DB connection */ char *archdbname; /* DB name *read* from archive */ - trivalue promptPassword; char *savedPassword; /* password for ropt->username, if known */ char *use_role; PGconn *connection; @@ -455,7 +454,7 @@ extern void InitArchiveFmt_Tar(ArchiveHandle *AH); extern bool isValidTarHeader(char *header); -extern void ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *newUser); +extern void ReconnectToServer(ArchiveHandle *AH, const char *dbname); extern void DropBlobIfExists(ArchiveHandle *AH, Oid oid); void ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH); diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c index 5e32ee8a5b..3c3a313180 100644 --- a/src/bin/pg_dump/pg_backup_db.c +++ b/src/bin/pg_dump/pg_backup_db.c @@ -30,7 +30,6 @@ static const char *modulename = gettext_noop("archiver (db)"); static void _check_database_version(ArchiveHandle *AH); -static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser); static void notice_processor(void *arg, const char *message); static void @@ -76,175 +75,51 @@ _check_database_version(ArchiveHandle *AH) /* * Reconnect to the server. If dbname is not NULL, use that database, - * else the one associated with the archive handle. If username is - * not NULL, use that user name, else the one from the handle. + * else the one associated with the archive handle. */ void -ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username) +ReconnectToServer(ArchiveHandle *AH, const char *dbname) { - PGconn *newConn; - const char *newdbname; - const char *newusername; - - if (!dbname) - newdbname = PQdb(AH->connection); - else - newdbname = dbname; - - if (!username) - newusername = PQuser(AH->connection); - else - newusername = username; - - newConn = _connectDB(AH, newdbname, newusername); - - /* Update ArchiveHandle's connCancel before closing old connection */ - set_archive_cancel_info(AH, newConn); - - PQfinish(AH->connection); - AH->connection = newConn; - - /* Start strict; later phases may override this. */ - PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH, - ALWAYS_SECURE_SEARCH_PATH_SQL)); -} - -/* - * Connect to the db again. - * - * Note: it's not really all that sensible to use a single-entry password - * cache if the username keeps changing. In current usage, however, the - * username never does change, so one savedPassword is sufficient. We do - * update the cache on the off chance that the password has changed since the - * start of the run. - */ -static PGconn * -_connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) -{ - PQExpBufferData connstr; - PGconn *newConn; - const char *newdb; - const char *newuser; - char *password; - char passbuf[100]; - bool new_pass; - - if (!reqdb) - newdb = PQdb(AH->connection); - else - newdb = reqdb; - - if (!requser || strlen(requser) == 0) - newuser = PQuser(AH->connection); - else - newuser = requser; - - ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n", - newdb, newuser); - - password = AH->savedPassword; - - if (AH->promptPassword == TRI_YES && password == NULL) - { - simple_prompt("Password: ", passbuf, sizeof(passbuf), false); - password = passbuf; - } - - initPQExpBuffer(&connstr); - appendPQExpBuffer(&connstr, "dbname="); - appendConnStrVal(&connstr, newdb); - - do - { - const char *keywords[7]; - const char *values[7]; - - keywords[0] = "host"; - values[0] = PQhost(AH->connection); - keywords[1] = "port"; - values[1] = PQport(AH->connection); - keywords[2] = "user"; - values[2] = newuser; - keywords[3] = "password"; - values[3] = password; - keywords[4] = "dbname"; - values[4] = connstr.data; - keywords[5] = "fallback_application_name"; - values[5] = progname; - keywords[6] = NULL; - values[6] = NULL; - - new_pass = false; - newConn = PQconnectdbParams(keywords, values, true); - - if (!newConn) - exit_horribly(modulename, "failed to reconnect to database\n"); - - if (PQstatus(newConn) == CONNECTION_BAD) - { - if (!PQconnectionNeedsPassword(newConn)) - exit_horribly(modulename, "could not reconnect to database: %s", - PQerrorMessage(newConn)); - PQfinish(newConn); - - if (password) - fprintf(stderr, "Password incorrect\n"); - - fprintf(stderr, "Connecting to %s as %s\n", - newdb, newuser); - - if (AH->promptPassword != TRI_NO) - { - simple_prompt("Password: ", passbuf, sizeof(passbuf), false); - password = passbuf; - } - else - exit_horribly(modulename, "connection needs password\n"); - - new_pass = true; - } - } while (new_pass); + PGconn *oldConn = AH->connection; + RestoreOptions *ropt = AH->public.ropt; /* - * We want to remember connection's actual password, whether or not we got - * it by prompting. So we don't just store the password variable. + * Save the dbname, if given, in override_dbname so that it will also + * affect any later reconnection attempt. */ - if (PQconnectionUsedPassword(newConn)) - { - if (AH->savedPassword) - free(AH->savedPassword); - AH->savedPassword = pg_strdup(PQpass(newConn)); - } + if (dbname) + ropt->cparams.override_dbname = pg_strdup(dbname); - termPQExpBuffer(&connstr); + /* + * Note: we want to establish the new connection, and in particular update + * ArchiveHandle's connCancel, before closing old connection. Otherwise + * an ill-timed SIGINT could try to access a dead connection. + */ + AH->connection = NULL; /* dodge error check in ConnectDatabase */ - /* check for version mismatch */ - _check_database_version(AH); + ConnectDatabase((Archive *) AH, &ropt->cparams, true); - PQsetNoticeProcessor(newConn, notice_processor, NULL); - - return newConn; + PQfinish(oldConn); } - /* - * Make a database connection with the given parameters. The - * connection handle is returned, the parameters are stored in AHX. - * An interactive password prompt is automatically issued if required. + * Make, or remake, a database connection with the given parameters. * + * The resulting connection handle is stored in AHX->connection. + * + * An interactive password prompt is automatically issued if required. + * We store the results of that in AHX->savedPassword. * Note: it's not really all that sensible to use a single-entry password * cache if the username keeps changing. In current usage, however, the * username never does change, so one savedPassword is sufficient. */ void ConnectDatabase(Archive *AHX, - const char *dbname, - const char *pghost, - const char *pgport, - const char *username, - trivalue prompt_password) + const ConnParams *cparams, + bool isReconnect) { ArchiveHandle *AH = (ArchiveHandle *) AHX; + trivalue prompt_password; char *password; char passbuf[100]; bool new_pass; @@ -252,6 +127,9 @@ ConnectDatabase(Archive *AHX, if (AH->connection) exit_horribly(modulename, "already connected to a database\n"); + /* Never prompt for a password during a reconnection */ + prompt_password = isReconnect ? TRI_NO : cparams->promptPassword; + password = AH->savedPassword; if (prompt_password == TRI_YES && password == NULL) @@ -259,7 +137,6 @@ ConnectDatabase(Archive *AHX, simple_prompt("Password: ", passbuf, sizeof(passbuf), false); password = passbuf; } - AH->promptPassword = prompt_password; /* * Start the connection. Loop until we have a password if requested by @@ -267,23 +144,35 @@ ConnectDatabase(Archive *AHX, */ do { - const char *keywords[7]; - const char *values[7]; + const char *keywords[8]; + const char *values[8]; + int i = 0; - keywords[0] = "host"; - values[0] = pghost; - keywords[1] = "port"; - values[1] = pgport; - keywords[2] = "user"; - values[2] = username; - keywords[3] = "password"; - values[3] = password; - keywords[4] = "dbname"; - values[4] = dbname; - keywords[5] = "fallback_application_name"; - values[5] = progname; - keywords[6] = NULL; - values[6] = NULL; + /* + * If dbname is a connstring, its entries can override the other + * values obtained from cparams; but in turn, override_dbname can + * override the dbname component of it. + */ + keywords[i] = "host"; + values[i++] = cparams->pghost; + keywords[i] = "port"; + values[i++] = cparams->pgport; + keywords[i] = "user"; + values[i++] = cparams->username; + keywords[i] = "password"; + values[i++] = password; + keywords[i] = "dbname"; + values[i++] = cparams->dbname; + if (cparams->override_dbname) + { + keywords[i] = "dbname"; + values[i++] = cparams->override_dbname; + } + keywords[i] = "fallback_application_name"; + values[i++] = progname; + keywords[i] = NULL; + values[i++] = NULL; + Assert(i <= lengthof(keywords)); new_pass = false; AH->connection = PQconnectdbParams(keywords, values, true); @@ -305,9 +194,16 @@ ConnectDatabase(Archive *AHX, /* check to see that the backend connection was successfully made */ if (PQstatus(AH->connection) == CONNECTION_BAD) - exit_horribly(modulename, "connection to database \"%s\" failed: %s", - PQdb(AH->connection) ? PQdb(AH->connection) : "", - PQerrorMessage(AH->connection)); + { + if (isReconnect) + exit_horribly(modulename, "reconnection to database \"%s\" failed: %s", + PQdb(AH->connection) ? PQdb(AH->connection) : "", + PQerrorMessage(AH->connection)); + else + exit_horribly(modulename, "connection to database \"%s\" failed: %s", + PQdb(AH->connection) ? PQdb(AH->connection) : "", + PQerrorMessage(AH->connection)); + } /* Start strict; later phases may override this. */ PQclear(ExecuteSqlQueryForSingleRow((Archive *) AH, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d6be54138e..ed6663e2d6 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -307,7 +307,6 @@ main(int argc, char **argv) const char *dumpsnapshot = NULL; char *use_role = NULL; int numWorkers = 1; - trivalue prompt_password = TRI_DEFAULT; int compressLevel = -1; int plainText = 0; ArchiveFormat archiveFormat = archUnknown; @@ -438,7 +437,7 @@ main(int argc, char **argv) break; case 'd': /* database name */ - dopt.dbname = pg_strdup(optarg); + dopt.cparams.dbname = pg_strdup(optarg); break; case 'E': /* Dump encoding */ @@ -454,7 +453,7 @@ main(int argc, char **argv) break; case 'h': /* server host */ - dopt.pghost = pg_strdup(optarg); + dopt.cparams.pghost = pg_strdup(optarg); break; case 'j': /* number of dump jobs */ @@ -479,7 +478,7 @@ main(int argc, char **argv) break; case 'p': /* server port */ - dopt.pgport = pg_strdup(optarg); + dopt.cparams.pgport = pg_strdup(optarg); break; case 'R': @@ -504,7 +503,7 @@ main(int argc, char **argv) break; case 'U': - dopt.username = pg_strdup(optarg); + dopt.cparams.username = pg_strdup(optarg); break; case 'v': /* verbose */ @@ -512,11 +511,11 @@ main(int argc, char **argv) break; case 'w': - prompt_password = TRI_NO; + dopt.cparams.promptPassword = TRI_NO; break; case 'W': - prompt_password = TRI_YES; + dopt.cparams.promptPassword = TRI_YES; break; case 'x': /* skip ACL dump */ @@ -570,8 +569,8 @@ main(int argc, char **argv) * Non-option argument specifies database name as long as it wasn't * already specified with -d / --dbname */ - if (optind < argc && dopt.dbname == NULL) - dopt.dbname = argv[optind++]; + if (optind < argc && dopt.cparams.dbname == NULL) + dopt.cparams.dbname = argv[optind++]; /* Complain if any arguments remain */ if (optind < argc) @@ -691,7 +690,7 @@ main(int argc, char **argv) * Open the database using the Archiver, so it knows about it. Errors mean * death. */ - ConnectDatabase(fout, dopt.dbname, dopt.pghost, dopt.pgport, dopt.username, prompt_password); + ConnectDatabase(fout, &dopt.cparams, false); setup_connection(fout, dumpencoding, dumpsnapshot, use_role); /* @@ -873,6 +872,11 @@ main(int argc, char **argv) ropt->filename = filename; /* if you change this list, see dumpOptionsFromRestoreOptions */ + ropt->cparams.dbname = dopt.cparams.dbname ? pg_strdup(dopt.cparams.dbname) : NULL; + ropt->cparams.pgport = dopt.cparams.pgport ? pg_strdup(dopt.cparams.pgport) : NULL; + ropt->cparams.pghost = dopt.cparams.pghost ? pg_strdup(dopt.cparams.pghost) : NULL; + ropt->cparams.username = dopt.cparams.username ? pg_strdup(dopt.cparams.username) : NULL; + ropt->cparams.promptPassword = dopt.cparams.promptPassword; ropt->dropSchema = dopt.outputClean; ropt->dataOnly = dopt.dataOnly; ropt->schemaOnly = dopt.schemaOnly; diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index f5df4e63d7..21e7fd80ea 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -165,7 +165,7 @@ main(int argc, char **argv) opts->createDB = 1; break; case 'd': - opts->dbname = pg_strdup(optarg); + opts->cparams.dbname = pg_strdup(optarg); break; case 'e': opts->exit_on_error = true; @@ -179,7 +179,7 @@ main(int argc, char **argv) break; case 'h': if (strlen(optarg) != 0) - opts->pghost = pg_strdup(optarg); + opts->cparams.pghost = pg_strdup(optarg); break; case 'j': /* number of restore jobs */ @@ -208,7 +208,7 @@ main(int argc, char **argv) case 'p': if (strlen(optarg) != 0) - opts->pgport = pg_strdup(optarg); + opts->cparams.pgport = pg_strdup(optarg); break; case 'R': /* no-op, still accepted for backwards compatibility */ @@ -242,7 +242,7 @@ main(int argc, char **argv) break; case 'U': - opts->username = pg_strdup(optarg); + opts->cparams.username = pg_strdup(optarg); break; case 'v': /* verbose */ @@ -250,11 +250,11 @@ main(int argc, char **argv) break; case 'w': - opts->promptPassword = TRI_NO; + opts->cparams.promptPassword = TRI_NO; break; case 'W': - opts->promptPassword = TRI_YES; + opts->cparams.promptPassword = TRI_YES; break; case 'x': /* skip ACL dump */ @@ -304,7 +304,7 @@ main(int argc, char **argv) } /* Should get at most one of -d and -f, else user is confused */ - if (opts->dbname) + if (opts->cparams.dbname) { if (opts->filename) {