Commit 83dec5a7 authored by Alvaro Herrera's avatar Alvaro Herrera

vacuumdb: don't prompt for passwords over and over

Having the script prompt for passwords over and over was a preexisting
problem when it processed multiple databases or when it processed
multiple analyze stages, but the parallel mode introduced in commit
a1792320 made it worse.

Fix the annoyance by keeping a copy of the password used by the first
connection that requires one.  Since users can (currently) only have a
single password, there's no need for more complex arrangements (such as
remembering one password per database).

Per bug #13741 reported by Eric Brown.  Patch authored and
cross-reviewed by Haribabu Kommi and Michael Paquier, slightly tweaked
by Álvaro Herrera.

Discussion: http://www.postgresql.org/message-id/20151027193919.931.54948@wrigleys.postgresql.org
Backpatch to 9.5, where parallel vacuumdb was introduced.
parent fe702a7b
...@@ -203,7 +203,7 @@ cluster_one_database(const char *dbname, bool verbose, const char *table, ...@@ -203,7 +203,7 @@ cluster_one_database(const char *dbname, bool verbose, const char *table,
appendPQExpBuffer(&sql, " %s", table); appendPQExpBuffer(&sql, " %s", table);
appendPQExpBufferChar(&sql, ';'); appendPQExpBufferChar(&sql, ';');
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, NULL, prompt_password,
progname, false); progname, false);
if (!executeMaintenanceCommand(conn, sql.data, echo)) if (!executeMaintenanceCommand(conn, sql.data, echo))
{ {
......
...@@ -52,19 +52,24 @@ handle_help_version_opts(int argc, char *argv[], ...@@ -52,19 +52,24 @@ handle_help_version_opts(int argc, char *argv[],
/* /*
* Make a database connection with the given parameters. An * Make a database connection with the given parameters.
* interactive password prompt is automatically issued if required. *
* A password can be given, but if not (or if user forces us to) we prompt
* interactively for one, unless caller prohibited us from doing so.
*/ */
PGconn * PGconn *
connectDatabase(const char *dbname, const char *pghost, const char *pgport, connectDatabase(const char *dbname, const char *pghost, const char *pgport,
const char *pguser, enum trivalue prompt_password, const char *pguser, const char *pgpassword,
const char *progname, bool fail_ok) enum trivalue prompt_password, const char *progname,
bool fail_ok)
{ {
PGconn *conn; PGconn *conn;
char *password = NULL; char *password;
bool new_pass; bool new_pass;
if (prompt_password == TRI_YES) password = pgpassword ? strdup(pgpassword) : NULL;
if (prompt_password == TRI_YES && !pgpassword)
password = simple_prompt("Password: ", 100, false); password = simple_prompt("Password: ", 100, false);
/* /*
...@@ -95,22 +100,26 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport, ...@@ -95,22 +100,26 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
new_pass = false; new_pass = false;
conn = PQconnectdbParams(keywords, values, true); conn = PQconnectdbParams(keywords, values, true);
free(keywords);
free(values);
if (!conn) if (!conn)
{ {
fprintf(stderr, _("%s: could not connect to database %s\n"), fprintf(stderr, _("%s: could not connect to database %s: out of memory\n"),
progname, dbname); progname, dbname);
exit(1); exit(1);
} }
pg_free(keywords);
pg_free(values);
/*
* No luck? Trying asking (again) for a password.
*/
if (PQstatus(conn) == CONNECTION_BAD && if (PQstatus(conn) == CONNECTION_BAD &&
PQconnectionNeedsPassword(conn) && PQconnectionNeedsPassword(conn) &&
password == NULL &&
prompt_password != TRI_NO) prompt_password != TRI_NO)
{ {
PQfinish(conn); PQfinish(conn);
if (password)
free(password);
password = simple_prompt("Password: ", 100, false); password = simple_prompt("Password: ", 100, false);
new_pass = true; new_pass = true;
} }
...@@ -148,14 +157,14 @@ connectMaintenanceDatabase(const char *maintenance_db, const char *pghost, ...@@ -148,14 +157,14 @@ connectMaintenanceDatabase(const char *maintenance_db, const char *pghost,
/* If a maintenance database name was specified, just connect to it. */ /* If a maintenance database name was specified, just connect to it. */
if (maintenance_db) if (maintenance_db)
return connectDatabase(maintenance_db, pghost, pgport, pguser, return connectDatabase(maintenance_db, pghost, pgport, pguser, NULL,
prompt_password, progname, false); prompt_password, progname, false);
/* Otherwise, try postgres first and then template1. */ /* Otherwise, try postgres first and then template1. */
conn = connectDatabase("postgres", pghost, pgport, pguser, prompt_password, conn = connectDatabase("postgres", pghost, pgport, pguser, NULL,
progname, true); prompt_password, progname, true);
if (!conn) if (!conn)
conn = connectDatabase("template1", pghost, pgport, pguser, conn = connectDatabase("template1", pghost, pgport, pguser, NULL,
prompt_password, progname, false); prompt_password, progname, false);
return conn; return conn;
......
...@@ -31,8 +31,8 @@ extern void handle_help_version_opts(int argc, char *argv[], ...@@ -31,8 +31,8 @@ extern void handle_help_version_opts(int argc, char *argv[],
extern PGconn *connectDatabase(const char *dbname, const char *pghost, extern PGconn *connectDatabase(const char *dbname, const char *pghost,
const char *pgport, const char *pguser, const char *pgport, const char *pguser,
enum trivalue prompt_password, const char *progname, const char *pgpassword, enum trivalue prompt_password,
bool fail_ok); const char *progname, bool fail_ok);
extern PGconn *connectMaintenanceDatabase(const char *maintenance_db, extern PGconn *connectMaintenanceDatabase(const char *maintenance_db,
const char *pghost, const char *pgport, const char *pguser, const char *pghost, const char *pgport, const char *pguser,
......
...@@ -140,8 +140,8 @@ main(int argc, char *argv[]) ...@@ -140,8 +140,8 @@ main(int argc, char *argv[])
printQueryOpt popt; printQueryOpt popt;
static const bool translate_columns[] = {false, true}; static const bool translate_columns[] = {false, true};
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, NULL,
progname, false); prompt_password, progname, false);
printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", " printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
"(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" " "(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
...@@ -180,8 +180,8 @@ main(int argc, char *argv[]) ...@@ -180,8 +180,8 @@ main(int argc, char *argv[])
if (*p >= 'A' && *p <= 'Z') if (*p >= 'A' && *p <= 'Z')
*p += ('a' - 'A'); *p += ('a' - 'A');
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, NULL,
progname, false); prompt_password, progname, false);
/* /*
* Make sure the language isn't already installed * Make sure the language isn't already installed
......
...@@ -250,8 +250,8 @@ main(int argc, char *argv[]) ...@@ -250,8 +250,8 @@ main(int argc, char *argv[])
if (login == 0) if (login == 0)
login = TRI_YES; login = TRI_YES;
conn = connectDatabase("postgres", host, port, username, prompt_password, conn = connectDatabase("postgres", host, port, username, NULL,
progname, false); prompt_password, progname, false);
initPQExpBuffer(&sql); initPQExpBuffer(&sql);
......
...@@ -139,8 +139,8 @@ main(int argc, char *argv[]) ...@@ -139,8 +139,8 @@ main(int argc, char *argv[])
printQueryOpt popt; printQueryOpt popt;
static const bool translate_columns[] = {false, true}; static const bool translate_columns[] = {false, true};
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, NULL,
progname, false); prompt_password, progname, false);
printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", " printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", "
"(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" " "(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" "
...@@ -181,8 +181,8 @@ main(int argc, char *argv[]) ...@@ -181,8 +181,8 @@ main(int argc, char *argv[])
if (*p >= 'A' && *p <= 'Z') if (*p >= 'A' && *p <= 'Z')
*p += ('a' - 'A'); *p += ('a' - 'A');
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, NULL,
progname, false); prompt_password, progname, false);
/* /*
* Force schema search path to be just pg_catalog, so that we don't have * Force schema search path to be just pg_catalog, so that we don't have
......
...@@ -128,8 +128,8 @@ main(int argc, char *argv[]) ...@@ -128,8 +128,8 @@ main(int argc, char *argv[])
appendPQExpBuffer(&sql, "DROP ROLE %s%s;", appendPQExpBuffer(&sql, "DROP ROLE %s%s;",
(if_exists ? "IF EXISTS " : ""), fmtId(dropuser)); (if_exists ? "IF EXISTS " : ""), fmtId(dropuser));
conn = connectDatabase("postgres", host, port, username, prompt_password, conn = connectDatabase("postgres", host, port, username, NULL,
progname, false); prompt_password, progname, false);
if (echo) if (echo)
printf("%s\n", sql.data); printf("%s\n", sql.data);
......
...@@ -297,8 +297,8 @@ reindex_one_database(const char *name, const char *dbname, const char *type, ...@@ -297,8 +297,8 @@ reindex_one_database(const char *name, const char *dbname, const char *type,
appendPQExpBuffer(&sql, " DATABASE %s", fmtId(name)); appendPQExpBuffer(&sql, " DATABASE %s", fmtId(name));
appendPQExpBufferChar(&sql, ';'); appendPQExpBufferChar(&sql, ';');
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, NULL,
progname, false); prompt_password, progname, false);
if (!executeMaintenanceCommand(conn, sql.data, echo)) if (!executeMaintenanceCommand(conn, sql.data, echo))
{ {
...@@ -372,8 +372,8 @@ reindex_system_catalogs(const char *dbname, const char *host, const char *port, ...@@ -372,8 +372,8 @@ reindex_system_catalogs(const char *dbname, const char *host, const char *port,
appendPQExpBuffer(&sql, " SYSTEM %s;", dbname); appendPQExpBuffer(&sql, " SYSTEM %s;", dbname);
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, NULL,
progname, false); prompt_password, progname, false);
if (!executeMaintenanceCommand(conn, sql.data, echo)) if (!executeMaintenanceCommand(conn, sql.data, echo))
{ {
fprintf(stderr, _("%s: reindexing of system catalogs failed: %s"), fprintf(stderr, _("%s: reindexing of system catalogs failed: %s"),
......
...@@ -43,7 +43,8 @@ static void vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, ...@@ -43,7 +43,8 @@ static void vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
const char *host, const char *port, const char *host, const char *port,
const char *username, enum trivalue prompt_password, const char *username, enum trivalue prompt_password,
int concurrentCons, int concurrentCons,
const char *progname, bool echo, bool quiet); const char *progname, bool echo, bool quiet,
char **password);
static void vacuum_all_databases(vacuumingOptions *vacopts, static void vacuum_all_databases(vacuumingOptions *vacopts,
bool analyze_in_stages, bool analyze_in_stages,
...@@ -275,6 +276,8 @@ main(int argc, char *argv[]) ...@@ -275,6 +276,8 @@ main(int argc, char *argv[])
} }
else else
{ {
char *password = NULL;
if (dbname == NULL) if (dbname == NULL)
{ {
if (getenv("PGDATABASE")) if (getenv("PGDATABASE"))
...@@ -296,7 +299,8 @@ main(int argc, char *argv[]) ...@@ -296,7 +299,8 @@ main(int argc, char *argv[])
&tables, &tables,
host, port, username, prompt_password, host, port, username, prompt_password,
concurrentCons, concurrentCons,
progname, echo, quiet); progname, echo, quiet,
&password);
} }
} }
else else
...@@ -305,7 +309,10 @@ main(int argc, char *argv[]) ...@@ -305,7 +309,10 @@ main(int argc, char *argv[])
&tables, &tables,
host, port, username, prompt_password, host, port, username, prompt_password,
concurrentCons, concurrentCons,
progname, echo, quiet); progname, echo, quiet,
&password);
pg_free(password);
} }
exit(0); exit(0);
...@@ -323,15 +330,21 @@ main(int argc, char *argv[]) ...@@ -323,15 +330,21 @@ main(int argc, char *argv[])
* If concurrentCons is > 1, multiple connections are used to vacuum tables * If concurrentCons is > 1, multiple connections are used to vacuum tables
* in parallel. In this case and if the table list is empty, we first obtain * in parallel. In this case and if the table list is empty, we first obtain
* a list of tables from the database. * a list of tables from the database.
*
* 'password' is both an input and output parameter. If one is not passed,
* then whatever is used in a connection is returned so that caller can
* reuse it in future connections.
*/ */
static void static void
vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
int stage, int stage,
SimpleStringList *tables, SimpleStringList *tables,
const char *host, const char *port, const char *host, const char *port,
const char *username, enum trivalue prompt_password, const char *username,
enum trivalue prompt_password,
int concurrentCons, int concurrentCons,
const char *progname, bool echo, bool quiet) const char *progname, bool echo, bool quiet,
char **password)
{ {
PQExpBufferData sql; PQExpBufferData sql;
PGconn *conn; PGconn *conn;
...@@ -365,8 +378,15 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, ...@@ -365,8 +378,15 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
fflush(stdout); fflush(stdout);
} }
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, *password,
progname, false); prompt_password, progname, false);
/*
* If no password was not specified by caller and the connection required
* one, remember it; this suppresses further password prompts.
*/
if (PQconnectionUsedPassword(conn) && *password == NULL)
*password = pg_strdup(PQpass(conn));
initPQExpBuffer(&sql); initPQExpBuffer(&sql);
...@@ -424,10 +444,20 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, ...@@ -424,10 +444,20 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
init_slot(slots, conn); init_slot(slots, conn);
if (parallel) if (parallel)
{ {
const char *pqpass;
/*
* If a password was supplied for the initial connection, use it for
* subsequent ones too. (Note that since we're connecting to the same
* database with the same user, there's no need to update the stored
* password any further.)
*/
pqpass = PQpass(conn);
for (i = 1; i < concurrentCons; i++) for (i = 1; i < concurrentCons; i++)
{ {
conn = connectDatabase(dbname, host, port, username, prompt_password, conn = connectDatabase(dbname, host, port, username, pqpass,
progname, false); prompt_password, progname, false);
init_slot(slots + i, conn); init_slot(slots + i, conn);
} }
} }
...@@ -542,12 +572,23 @@ vacuum_all_databases(vacuumingOptions *vacopts, ...@@ -542,12 +572,23 @@ vacuum_all_databases(vacuumingOptions *vacopts,
PGresult *result; PGresult *result;
int stage; int stage;
int i; int i;
char *password = NULL;
conn = connectMaintenanceDatabase(maintenance_db, host, port, conn = connectMaintenanceDatabase(maintenance_db, host, port,
username, prompt_password, progname); username, prompt_password, progname);
result = executeQuery(conn, result = executeQuery(conn,
"SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;",
progname, echo); progname, echo);
/*
* Remember the password for further connections. If no password was
* required for the maintenance db connection, this gets updated for the
* first connection that does.
*/
if (PQconnectionUsedPassword(conn))
password = pg_strdup(PQpass(conn));
PQfinish(conn); PQfinish(conn);
if (analyze_in_stages) if (analyze_in_stages)
...@@ -572,7 +613,8 @@ vacuum_all_databases(vacuumingOptions *vacopts, ...@@ -572,7 +613,8 @@ vacuum_all_databases(vacuumingOptions *vacopts,
NULL, NULL,
host, port, username, prompt_password, host, port, username, prompt_password,
concurrentCons, concurrentCons,
progname, echo, quiet); progname, echo, quiet,
&password);
} }
} }
} }
...@@ -588,11 +630,13 @@ vacuum_all_databases(vacuumingOptions *vacopts, ...@@ -588,11 +630,13 @@ vacuum_all_databases(vacuumingOptions *vacopts,
NULL, NULL,
host, port, username, prompt_password, host, port, username, prompt_password,
concurrentCons, concurrentCons,
progname, echo, quiet); progname, echo, quiet,
&password);
} }
} }
PQclear(result); PQclear(result);
pg_free(password);
} }
/* /*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment