/*------------------------------------------------------------------------- * * pg_backup_db.c * * Implements the basic DB functions used by the archiver. * * IDENTIFICATION * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.77.2.1 2008/08/16 02:25:11 tgl Exp $ * *------------------------------------------------------------------------- */ #include "pg_backup_db.h" #include "dumputils.h" #include #include #ifdef HAVE_TERMIOS_H #include #endif #define DB_MAX_ERR_STMT 128 static const char *modulename = gettext_noop("archiver (db)"); static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion); static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser); static void notice_processor(void *arg, const char *message); static int _parse_version(ArchiveHandle *AH, const char *versionString) { int v; v = parse_version(versionString); if (v < 0) die_horribly(AH, modulename, "could not parse version string \"%s\"\n", versionString); return v; } static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion) { int myversion; const char *remoteversion_str; int remoteversion; myversion = _parse_version(AH, PG_VERSION); remoteversion_str = PQparameterStatus(AH->connection, "server_version"); if (!remoteversion_str) die_horribly(AH, modulename, "could not get server_version from libpq\n"); remoteversion = _parse_version(AH, remoteversion_str); AH->public.remoteVersionStr = strdup(remoteversion_str); AH->public.remoteVersion = remoteversion; if (myversion != remoteversion && (remoteversion < AH->public.minRemoteVersion || remoteversion > AH->public.maxRemoteVersion)) { write_msg(NULL, "server version: %s; %s version: %s\n", remoteversion_str, progname, PG_VERSION); if (ignoreVersion) write_msg(NULL, "proceeding despite version mismatch\n"); else die_horribly(AH, NULL, "aborting because of version mismatch (Use the -i option to proceed anyway.)\n"); } } /* * 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. If * both the database and the user match the existing connection already, * nothing will be done. * * Returns 1 in any case. */ int ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username) { 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; /* Let's see if the request is already satisfied */ if (strcmp(newdbname, PQdb(AH->connection)) == 0 && strcmp(newusername, PQuser(AH->connection)) == 0) return 1; newConn = _connectDB(AH, newdbname, newusername); PQfinish(AH->connection); AH->connection = newConn; return 1; } /* * Connect to the db again. */ static PGconn * _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser) { PGconn *newConn; char *newdb; char *newuser; char *password = NULL; bool new_pass; if (!reqdb) newdb = PQdb(AH->connection); else newdb = (char *) reqdb; if (!requser || (strlen(requser) == 0)) newuser = PQuser(AH->connection); else newuser = (char *) requser; ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n", newdb, newuser); if (AH->requirePassword) { password = simple_prompt("Password: ", 100, false); if (password == NULL) die_horribly(AH, modulename, "out of memory\n"); } do { new_pass = false; newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection), NULL, NULL, newdb, newuser, password); if (!newConn) die_horribly(AH, modulename, "failed to reconnect to database\n"); if (PQstatus(newConn) == CONNECTION_BAD) { if (!PQconnectionNeedsPassword(newConn)) die_horribly(AH, 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 (password) free(password); password = simple_prompt("Password: ", 100, false); new_pass = true; } } while (new_pass); if (password) free(password); /* check for version mismatch */ _check_database_version(AH, true); PQsetNoticeProcessor(newConn, notice_processor, NULL); return newConn; } /* * 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. */ PGconn * ConnectDatabase(Archive *AHX, const char *dbname, const char *pghost, const char *pgport, const char *username, const int reqPwd, const int ignoreVersion) { ArchiveHandle *AH = (ArchiveHandle *) AHX; char *password = NULL; bool new_pass; if (AH->connection) die_horribly(AH, modulename, "already connected to a database\n"); if (reqPwd) { password = simple_prompt("Password: ", 100, false); if (password == NULL) die_horribly(AH, modulename, "out of memory\n"); AH->requirePassword = true; } else AH->requirePassword = false; /* * Start the connection. Loop until we have a password if requested by * backend. */ do { new_pass = false; AH->connection = PQsetdbLogin(pghost, pgport, NULL, NULL, dbname, username, password); if (!AH->connection) die_horribly(AH, modulename, "failed to connect to database\n"); if (PQstatus(AH->connection) == CONNECTION_BAD && PQconnectionNeedsPassword(AH->connection) && password == NULL && !feof(stdin)) { PQfinish(AH->connection); password = simple_prompt("Password: ", 100, false); new_pass = true; } } while (new_pass); if (password) free(password); /* check to see that the backend connection was successfully made */ if (PQstatus(AH->connection) == CONNECTION_BAD) die_horribly(AH, modulename, "connection to database \"%s\" failed: %s", PQdb(AH->connection), PQerrorMessage(AH->connection)); /* check for version mismatch */ _check_database_version(AH, ignoreVersion); PQsetNoticeProcessor(AH->connection, notice_processor, NULL); return AH->connection; } static void notice_processor(void *arg, const char *message) { write_msg(NULL, "%s", message); } /* * Convenience function to send a query. * Monitors result to detect COPY statements */ static void ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc) { PGconn *conn = AH->connection; PGresult *res; char errStmt[DB_MAX_ERR_STMT]; #ifdef NOT_USED fprintf(stderr, "Executing: '%s'\n\n", qry); #endif res = PQexec(conn, qry); switch (PQresultStatus(res)) { case PGRES_COMMAND_OK: case PGRES_TUPLES_OK: case PGRES_EMPTY_QUERY: /* A-OK */ break; case PGRES_COPY_IN: /* Assume this is an expected result */ AH->pgCopyIn = true; break; default: /* trouble */ strncpy(errStmt, qry, DB_MAX_ERR_STMT); if (errStmt[DB_MAX_ERR_STMT - 1] != '\0') { errStmt[DB_MAX_ERR_STMT - 4] = '.'; errStmt[DB_MAX_ERR_STMT - 3] = '.'; errStmt[DB_MAX_ERR_STMT - 2] = '.'; errStmt[DB_MAX_ERR_STMT - 1] = '\0'; } warn_or_die_horribly(AH, modulename, "%s: %s Command was: %s\n", desc, PQerrorMessage(conn), errStmt); break; } PQclear(res); } /* * Implement ahwrite() for direct-to-DB restore */ int ExecuteSqlCommandBuf(ArchiveHandle *AH, const char *buf, size_t bufLen) { if (AH->writingCopyData) { /* * We drop the data on the floor if libpq has failed to enter COPY * mode; this allows us to behave reasonably when trying to continue * after an error in a COPY command. */ if (AH->pgCopyIn && PQputCopyData(AH->connection, buf, bufLen) <= 0) die_horribly(AH, modulename, "error returned by PQputCopyData: %s", PQerrorMessage(AH->connection)); } else { /* * In most cases the data passed to us will be a null-terminated * string, but if it's not, we have to add a trailing null. */ if (buf[bufLen] == '\0') ExecuteSqlCommand(AH, buf, "could not execute query"); else { char *str = (char *) malloc(bufLen + 1); if (!str) die_horribly(AH, modulename, "out of memory\n"); memcpy(str, buf, bufLen); str[bufLen] = '\0'; ExecuteSqlCommand(AH, str, "could not execute query"); free(str); } } return 1; } /* * Terminate a COPY operation during direct-to-DB restore */ void EndDBCopyMode(ArchiveHandle *AH, TocEntry *te) { if (AH->pgCopyIn) { PGresult *res; if (PQputCopyEnd(AH->connection, NULL) <= 0) die_horribly(AH, modulename, "error returned by PQputCopyEnd: %s", PQerrorMessage(AH->connection)); /* Check command status and return to normal libpq state */ res = PQgetResult(AH->connection); if (PQresultStatus(res) != PGRES_COMMAND_OK) warn_or_die_horribly(AH, modulename, "COPY failed for table \"%s\": %s", te->tag, PQerrorMessage(AH->connection)); PQclear(res); AH->pgCopyIn = false; } } void StartTransaction(ArchiveHandle *AH) { ExecuteSqlCommand(AH, "BEGIN", "could not start database transaction"); } void CommitTransaction(ArchiveHandle *AH) { ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction"); }