/*------------------------------------------------------------------------- * * 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.88 2010/02/18 01:29:10 tgl Exp $ * *------------------------------------------------------------------------- */ #include "pg_backup_db.h" #include "dumputils.h" #include #include #ifdef HAVE_TERMIOS_H #include #endif 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 char *_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos); static char *_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos); static bool _isIdentChar(unsigned char c); static bool _isDQChar(unsigned char c, bool atStart); #define DB_MAX_ERR_STMT 128 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) { 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); die_horribly(AH, NULL, "aborting because of server version mismatch\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. * * 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) { PGconn *newConn; const char *newdb; const char *newuser; char *password = AH->savedPassword; 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); if (AH->promptPassword == TRI_YES && password == NULL) { password = simple_prompt("Password: ", 100, false); if (password == NULL) die_horribly(AH, modulename, "out of memory\n"); } do { #define PARAMS_ARRAY_SIZE 7 const char **keywords = malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); const char **values = malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); if (!keywords || !values) die_horribly(AH, modulename, "out of memory\n"); 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] = newdb; keywords[5] = "fallback_application_name"; values[5] = progname; keywords[6] = NULL; values[6] = NULL; new_pass = false; newConn = PQconnectdbParams(keywords, values, true); free(keywords); free(values); 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); if (AH->promptPassword != TRI_NO) password = simple_prompt("Password: ", 100, false); else die_horribly(AH, modulename, "connection needs password\n"); if (password == NULL) die_horribly(AH, modulename, "out of memory\n"); new_pass = true; } } while (new_pass); AH->savedPassword = password; /* check for version mismatch */ _check_database_version(AH); 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. * * 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. */ PGconn * ConnectDatabase(Archive *AHX, const char *dbname, const char *pghost, const char *pgport, const char *username, enum trivalue prompt_password) { ArchiveHandle *AH = (ArchiveHandle *) AHX; char *password = AH->savedPassword; bool new_pass; if (AH->connection) die_horribly(AH, modulename, "already connected to a database\n"); if (prompt_password == TRI_YES && password == NULL) { password = simple_prompt("Password: ", 100, false); if (password == NULL) die_horribly(AH, modulename, "out of memory\n"); } AH->promptPassword = prompt_password; /* * Start the connection. Loop until we have a password if requested by * backend. */ do { #define PARAMS_ARRAY_SIZE 7 const char **keywords = malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); const char **values = malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); if (!keywords || !values) die_horribly(AH, modulename, "out of memory\n"); 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; new_pass = false; AH->connection = PQconnectdbParams(keywords, values, true); free(keywords); free(values); if (!AH->connection) die_horribly(AH, modulename, "failed to connect to database\n"); if (PQstatus(AH->connection) == CONNECTION_BAD && PQconnectionNeedsPassword(AH->connection) && password == NULL && prompt_password != TRI_NO) { PQfinish(AH->connection); password = simple_prompt("Password: ", 100, false); if (password == NULL) die_horribly(AH, modulename, "out of memory\n"); new_pass = true; } } while (new_pass); AH->savedPassword = 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); PQsetNoticeProcessor(AH->connection, notice_processor, NULL); return AH->connection; } static void notice_processor(void *arg, const char *message) { write_msg(NULL, "%s", message); } /* Public interface */ /* Convenience function to send a query. Monitors result to handle 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: /* 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); } /* * Used by ExecuteSqlCommandBuf to send one buffered line when running a COPY command. */ static char * _sendCopyLine(ArchiveHandle *AH, char *qry, char *eos) { size_t loc; /* Location of next newline */ int pos = 0; /* Current position */ int sPos = 0; /* Last pos of a slash char */ int isEnd = 0; /* loop to find unquoted newline ending the line of COPY data */ for (;;) { loc = strcspn(&qry[pos], "\n") + pos; /* If no match, then wait */ if (loc >= (eos - qry)) /* None found */ { appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry)); return eos; } /* * fprintf(stderr, "Found cr at %d, prev char was %c, next was %c\n", * loc, qry[loc-1], qry[loc+1]); */ /* Count the number of preceding slashes */ sPos = loc; while (sPos > 0 && qry[sPos - 1] == '\\') sPos--; sPos = loc - sPos; /* * If an odd number of preceding slashes, then \n was escaped so set * the next search pos, and loop (if any left). */ if ((sPos & 1) == 1) { /* fprintf(stderr, "cr was escaped\n"); */ pos = loc + 1; if (pos >= (eos - qry)) { appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry)); return eos; } } else break; } /* We found an unquoted newline */ qry[loc] = '\0'; appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry); isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0); /* * Note that 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, AH->pgCopyBuf->data, AH->pgCopyBuf->len) <= 0) die_horribly(AH, modulename, "error returned by PQputCopyData: %s", PQerrorMessage(AH->connection)); resetPQExpBuffer(AH->pgCopyBuf); if (isEnd && 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: %s", PQerrorMessage(AH->connection)); PQclear(res); AH->pgCopyIn = false; } return qry + loc + 1; } /* * Used by ExecuteSqlCommandBuf to send one buffered line of SQL * (not data for the copy command). */ static char * _sendSQLLine(ArchiveHandle *AH, char *qry, char *eos) { /* * The following is a mini state machine to assess the end of an SQL * statement. It really only needs to parse good SQL, or at least that's * the theory... End-of-statement is assumed to be an unquoted, * un-commented semi-colon that's not within any parentheses. * * Note: the input can be split into bufferloads at arbitrary boundaries. * Therefore all state must be kept in AH->sqlparse, not in local * variables of this routine. We assume that AH->sqlparse was filled with * zeroes when created. */ for (; qry < eos; qry++) { switch (AH->sqlparse.state) { case SQL_SCAN: /* Default state == 0, set in _allocAH */ if (*qry == ';' && AH->sqlparse.braceDepth == 0) { /* * We've found the end of a statement. Send it and reset * the buffer. */ appendPQExpBufferChar(AH->sqlBuf, ';'); /* inessential */ ExecuteSqlCommand(AH, AH->sqlBuf->data, "could not execute query"); resetPQExpBuffer(AH->sqlBuf); AH->sqlparse.lastChar = '\0'; /* * Remove any following newlines - so that embedded COPY * commands don't get a starting newline. */ qry++; while (qry < eos && *qry == '\n') qry++; /* We've finished one line, so exit */ return qry; } else if (*qry == '\'') { if (AH->sqlparse.lastChar == 'E') AH->sqlparse.state = SQL_IN_E_QUOTE; else AH->sqlparse.state = SQL_IN_SINGLE_QUOTE; AH->sqlparse.backSlash = false; } else if (*qry == '"') { AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE; } /* * Look for dollar-quotes. We make the assumption that * $-quotes will not have an ident character just before them * in pg_dump output. XXX is this good enough? */ else if (*qry == '$' && !_isIdentChar(AH->sqlparse.lastChar)) { AH->sqlparse.state = SQL_IN_DOLLAR_TAG; /* initialize separate buffer with possible tag */ if (AH->sqlparse.tagBuf == NULL) AH->sqlparse.tagBuf = createPQExpBuffer(); else resetPQExpBuffer(AH->sqlparse.tagBuf); appendPQExpBufferChar(AH->sqlparse.tagBuf, *qry); } else if (*qry == '-' && AH->sqlparse.lastChar == '-') AH->sqlparse.state = SQL_IN_SQL_COMMENT; else if (*qry == '*' && AH->sqlparse.lastChar == '/') AH->sqlparse.state = SQL_IN_EXT_COMMENT; else if (*qry == '(') AH->sqlparse.braceDepth++; else if (*qry == ')') AH->sqlparse.braceDepth--; break; case SQL_IN_SQL_COMMENT: if (*qry == '\n') AH->sqlparse.state = SQL_SCAN; break; case SQL_IN_EXT_COMMENT: /* * This isn't fully correct, because we don't account for * nested slash-stars, but pg_dump never emits such. */ if (AH->sqlparse.lastChar == '*' && *qry == '/') AH->sqlparse.state = SQL_SCAN; break; case SQL_IN_SINGLE_QUOTE: /* We needn't handle '' specially */ if (*qry == '\'' && !AH->sqlparse.backSlash) AH->sqlparse.state = SQL_SCAN; else if (*qry == '\\') AH->sqlparse.backSlash = !AH->sqlparse.backSlash; else AH->sqlparse.backSlash = false; break; case SQL_IN_E_QUOTE: /* * Eventually we will need to handle '' specially, because * after E'...''... we should still be in E_QUOTE state. * * XXX problem: how do we tell whether the dump was made by a * version that thinks backslashes aren't special in non-E * literals?? */ if (*qry == '\'' && !AH->sqlparse.backSlash) AH->sqlparse.state = SQL_SCAN; else if (*qry == '\\') AH->sqlparse.backSlash = !AH->sqlparse.backSlash; else AH->sqlparse.backSlash = false; break; case SQL_IN_DOUBLE_QUOTE: /* We needn't handle "" specially */ if (*qry == '"') AH->sqlparse.state = SQL_SCAN; break; case SQL_IN_DOLLAR_TAG: if (*qry == '$') { /* Do not add the closing $ to tagBuf */ AH->sqlparse.state = SQL_IN_DOLLAR_QUOTE; AH->sqlparse.minTagEndPos = AH->sqlBuf->len + AH->sqlparse.tagBuf->len + 1; } else if (_isDQChar(*qry, (AH->sqlparse.tagBuf->len == 1))) { /* Valid, so add to tag */ appendPQExpBufferChar(AH->sqlparse.tagBuf, *qry); } else { /* * Ooops, we're not really in a dollar-tag. Valid tag * chars do not include the various chars we look for in * this state machine, so it's safe to just jump from this * state back to SCAN. We have to back up the qry pointer * so that the current character gets rescanned in SCAN * state; and then "continue" so that the bottom-of-loop * actions aren't done yet. */ AH->sqlparse.state = SQL_SCAN; qry--; continue; } break; case SQL_IN_DOLLAR_QUOTE: /* * If we are at a $, see whether what precedes it matches * tagBuf. (Remember that the trailing $ of the tag was not * added to tagBuf.) However, don't compare until we have * enough data to be a possible match --- this is needed to * avoid false match on '$a$a$...' */ if (*qry == '$' && AH->sqlBuf->len >= AH->sqlparse.minTagEndPos && strcmp(AH->sqlparse.tagBuf->data, AH->sqlBuf->data + AH->sqlBuf->len - AH->sqlparse.tagBuf->len) == 0) AH->sqlparse.state = SQL_SCAN; break; } appendPQExpBufferChar(AH->sqlBuf, *qry); AH->sqlparse.lastChar = *qry; } /* * If we get here, we've processed entire bufferload with no complete SQL * stmt */ return eos; } /* Convenience function to send one or more queries. Monitors result to handle COPY statements */ int ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, size_t bufLen) { char *qry = (char *) qryv; char *eos = qry + bufLen; /* * fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n", * qry); */ /* Could switch between command and COPY IN mode at each line */ while (qry < eos) { /* * If libpq is in CopyIn mode *or* if the archive structure shows we * are sending COPY data, treat the data as COPY data. The pgCopyIn * check is only needed for backwards compatibility with ancient * archive files that might just issue a COPY command without marking * it properly. Note that in an archive entry that has a copyStmt, * all data up to the end of the entry will go to _sendCopyLine, and * therefore will be dropped if libpq has failed to enter COPY mode. * Also, if a "\." data terminator is found, anything remaining in the * archive entry will be dropped. */ if (AH->pgCopyIn || AH->writingCopyData) qry = _sendCopyLine(AH, qry, eos); else qry = _sendSQLLine(AH, qry, eos); } return 1; } void StartTransaction(ArchiveHandle *AH) { ExecuteSqlCommand(AH, "BEGIN", "could not start database transaction"); } void CommitTransaction(ArchiveHandle *AH) { ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction"); } void DropBlobIfExists(ArchiveHandle *AH, Oid oid) { /* * If we are not restoring to a direct database connection, we have to * guess about how to detect whether the blob exists. Assume new-style. */ if (AH->connection == NULL || PQserverVersion(AH->connection) >= 90000) { ahprintf(AH, "SELECT pg_catalog.lo_unlink(oid) " "FROM pg_catalog.pg_largeobject_metadata " "WHERE oid = '%u';\n", oid); } else { /* Restoring to pre-9.0 server, so do it the old way */ ahprintf(AH, "SELECT CASE WHEN EXISTS(" "SELECT 1 FROM pg_catalog.pg_largeobject WHERE loid = '%u'" ") THEN pg_catalog.lo_unlink('%u') END;\n", oid, oid); } } static bool _isIdentChar(unsigned char c) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_') || (c == '$') || (c >= (unsigned char) '\200') /* no need to check <= \377 */ ) return true; else return false; } static bool _isDQChar(unsigned char c, bool atStart) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') || (!atStart && c >= '0' && c <= '9') || (c >= (unsigned char) '\200') /* no need to check <= \377 */ ) return true; else return false; }