1 /*-------------------------------------------------------------------------
4 *-------------------------------------------------------------------------
7 #include <unistd.h> /* for getopt() */
16 #include "access/attnum.h"
17 #include "access/htup.h"
18 #include "catalog/pg_index.h"
19 #include "catalog/pg_language.h"
20 #include "catalog/pg_trigger.h"
21 #include "catalog/pg_type.h"
24 #include <libpq/libpq-fs.h>
30 #include "pg_backup.h"
31 #include "pg_backup_archiver.h"
32 #include "pg_backup_db.h"
34 static const char *progname = "Archiver(db)";
36 static void _prompt_for_password(char *username, char *password);
37 static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
38 static PGconn* _connectDB(ArchiveHandle *AH, const char* newdbname, char *newUser);
39 static int _executeSqlCommand(ArchiveHandle* AH, PGconn *conn, PQExpBuffer qry, char *desc);
43 _prompt_for_password(char *username, char *password)
49 struct termios t_orig,
54 * Allow for forcing a specific username
56 if (strlen(username) == 0)
58 fprintf(stderr, "Username: ");
60 fgets(username, 100, stdin);
61 length = strlen(username);
62 /* skip rest of the line */
63 if (length > 0 && username[length - 1] != '\n')
67 fgets(buf, 512, stdin);
68 } while (buf[strlen(buf) - 1] != '\n');
70 if (length > 0 && username[length - 1] == '\n')
71 username[length - 1] = '\0';
78 tcsetattr(0, TCSADRAIN, &t);
80 fprintf(stderr, "Password: ");
82 fgets(password, 100, stdin);
84 tcsetattr(0, TCSADRAIN, &t_orig);
87 length = strlen(password);
88 /* skip rest of the line */
89 if (length > 0 && password[length - 1] != '\n')
93 fgets(buf, 512, stdin);
94 } while (buf[strlen(buf) - 1] != '\n');
96 if (length > 0 && password[length - 1] == '\n')
97 password[length - 1] = '\0';
99 fprintf(stderr, "\n\n");
104 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
108 const char *remoteversion_str;
109 double remoteversion;
110 PGconn *conn = AH->connection;
112 myversion = strtod(PG_VERSION, NULL);
113 res = PQexec(conn, "SELECT version()");
115 PQresultStatus(res) != PGRES_TUPLES_OK ||
118 die_horribly(AH, "check_database_version(): command failed. "
119 "Explanation from backend: '%s'.\n", PQerrorMessage(conn));
121 remoteversion_str = PQgetvalue(res, 0, 0);
122 remoteversion = strtod(remoteversion_str + 11, NULL);
123 if (myversion != remoteversion)
125 fprintf(stderr, "Database version: %s\n%s version: %s\n",
126 remoteversion_str, progname, PG_VERSION);
128 fprintf(stderr, "Proceeding despite version mismatch.\n");
130 die_horribly(AH, "Aborting because of version mismatch.\n"
131 "Use --ignore-version if you think it's safe to proceed anyway.\n");
137 * Check if a given user is a superuser.
139 int UserIsSuperuser(ArchiveHandle *AH, char* user)
141 PQExpBuffer qry = createPQExpBuffer();
147 /* Get the superuser setting */
148 appendPQExpBuffer(qry, "select usesuper from pg_user where usename = '%s'", user);
149 res = PQexec(AH->connection, qry->data);
152 die_horribly(AH, "%s: null result checking superuser status of %s.\n",
155 if (PQresultStatus(res) != PGRES_TUPLES_OK)
156 die_horribly(AH, "%s: Could not check superuser status of %s. Explanation from backend: %s\n",
157 progname, user, PQerrorMessage(AH->connection));
159 ntups = PQntuples(res);
165 i_usesuper = PQfnumber(res, "usesuper");
166 isSuper = (strcmp(PQgetvalue(res, 0, i_usesuper), "t") == 0);
173 int ConnectedUserIsSuperuser(ArchiveHandle *AH)
175 return UserIsSuperuser(AH, PQuser(AH->connection));
178 char* ConnectedUser(ArchiveHandle *AH)
180 return PQuser(AH->connection);
184 * Reconnect the DB associated with the archive handle
186 int ReconnectDatabase(ArchiveHandle *AH, const char* newdbname, char *newUser)
191 if (!newdbname || (strcmp(newdbname, "-") == 0) )
192 dbname = PQdb(AH->connection);
194 dbname = (char*)newdbname;
196 /* Let's see if the request is already satisfied */
197 if (strcmp(PQuser(AH->connection), newUser) == 0 && strcmp(newdbname, PQdb(AH->connection)) == 0)
200 newConn = _connectDB(AH, dbname, newUser);
202 PQfinish(AH->connection);
203 AH->connection = newConn;
204 strcpy(AH->username, newUser);
210 * Connect to the db again.
212 static PGconn* _connectDB(ArchiveHandle *AH, const char* reqdb, char *requser)
217 char *pwparam = NULL;
223 if (!reqdb || (strcmp(reqdb, "-") == 0) )
224 newdb = PQdb(AH->connection);
226 newdb = (char*)reqdb;
228 if (!requser || (strlen(requser) == 0))
229 newuser = PQuser(AH->connection);
231 newuser = (char*)requser;
233 ahlog(AH, 1, "Connecting to %s as %s\n", newdb, newuser);
238 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
242 die_horribly(AH, "%s: Failed to reconnect (PQsetdbLogin failed).\n", progname);
244 if (PQstatus(newConn) == CONNECTION_BAD)
246 noPwd = (strcmp(PQerrorMessage(newConn), "fe_sendauth: no password supplied\n") == 0);
247 badPwd = (strncmp(PQerrorMessage(newConn), "Password authentication failed for user", 39)
254 fprintf(stderr, "Password incorrect\n");
256 fprintf(stderr, "Connecting to %s as %s\n", PQdb(AH->connection), newuser);
259 _prompt_for_password(newuser, password);
263 die_horribly(AH, "%s: Could not reconnect. %s\n", progname, PQerrorMessage(newConn));
272 PGconn* ConnectDatabase(Archive *AHX,
277 const int ignoreVersion)
279 ArchiveHandle *AH = (ArchiveHandle*)AHX;
280 char connect_string[512] = "";
281 char tmp_string[128];
285 die_horribly(AH, "%s: already connected to database\n", progname);
287 if (!dbname && !(dbname = getenv("PGDATABASE")) )
288 die_horribly(AH, "%s: no database name specified\n", progname);
290 AH->dbname = strdup(dbname);
294 AH->pghost = strdup(pghost);
295 sprintf(tmp_string, "host=%s ", AH->pghost);
296 strcat(connect_string, tmp_string);
303 AH->pgport = strdup(pgport);
304 sprintf(tmp_string, "port=%s ", AH->pgport);
305 strcat(connect_string, tmp_string);
310 sprintf(tmp_string, "dbname=%s ", AH->dbname);
311 strcat(connect_string, tmp_string);
315 AH->username[0] = '\0';
316 _prompt_for_password(AH->username, password);
317 strcat(connect_string, "authtype=password ");
318 sprintf(tmp_string, "user=%s ", AH->username);
319 strcat(connect_string, tmp_string);
320 sprintf(tmp_string, "password=%s ", password);
321 strcat(connect_string, tmp_string);
322 MemSet(tmp_string, 0, sizeof(tmp_string));
323 MemSet(password, 0, sizeof(password));
325 AH->connection = PQconnectdb(connect_string);
326 MemSet(connect_string, 0, sizeof(connect_string));
328 /* check to see that the backend connection was successfully made */
329 if (PQstatus(AH->connection) == CONNECTION_BAD)
330 die_horribly(AH, "Connection to database '%s' failed.\n%s\n",
331 AH->dbname, PQerrorMessage(AH->connection));
333 /* check for version mismatch */
334 _check_database_version(AH, ignoreVersion);
337 * AH->currUser = PQuser(AH->connection);
339 * Removed because it prevented an initial \connect
340 * when dumping to SQL in pg_dump.
343 return AH->connection;
346 /* Public interface */
347 /* Convenience function to send a query. Monitors result to handle COPY statements */
348 int ExecuteSqlCommand(ArchiveHandle* AH, PQExpBuffer qry, char *desc)
350 return _executeSqlCommand(AH, AH->connection, qry, desc);
354 * Handle command execution. This is used to execute a command on more than one connection,
355 * but the 'pgCopyIn' setting assumes the COPY commands are ONLY executed on the primary
356 * setting...an error will be raised otherwise.
358 static int _executeSqlCommand(ArchiveHandle* AH, PGconn *conn, PQExpBuffer qry, char *desc)
362 /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
363 res = PQexec(conn, qry->data);
365 die_horribly(AH, "%s: %s. No result from backend.\n", progname, desc);
367 if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
369 if (PQresultStatus(res) == PGRES_COPY_IN)
371 if (conn != AH->connection)
372 die_horribly(AH, "%s: COPY command execute in non-primary connection.\n", progname);
377 die_horribly(AH, "%s: %s. Code = %d. Explanation from backend: '%s'.\n",
378 progname, desc, PQresultStatus(res), PQerrorMessage(AH->connection));
383 return strlen(qry->data);
386 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
387 int ExecuteSqlCommandBuf(ArchiveHandle* AH, void *qryv, int bufLen)
392 char *qry = (char*)qryv;
394 char *eos = qry + bufLen;
396 /* fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n", qry); */
398 /* If we're in COPY IN mode, then just break it into lines and send... */
403 loc = strcspn(&qry[pos], "\n") + pos;
406 /* If no match, then wait */
407 if (loc >= (eos - qry)) /* None found */
409 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
413 /* fprintf(stderr, "Found cr at %d, prev char was %c, next was %c\n", loc, qry[loc-1], qry[loc+1]); */
415 /* Count the number of preceding slashes */
417 while (sPos > 0 && qry[sPos-1] == '\\')
422 /* If an odd number of preceding slashes, then \n was escaped
423 * so set the next search pos, and restart (if any left).
427 /* fprintf(stderr, "cr was escaped\n"); */
429 if (pos >= (eos - qry))
431 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
437 /* We got a good cr */
439 appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
441 isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
443 /* fprintf(stderr, "Sending '%s' via COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd); */
445 PQputline(AH->connection, AH->pgCopyBuf->data);
447 resetPQExpBuffer(AH->pgCopyBuf);
449 /* fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data); */
452 PQendcopy(AH->connection);
459 /* Make sure we're not past the original buffer end */
466 /* We may have finished Copy In, and have a non-empty buffer */
470 * The following is a mini state machine to assess then of of an SQL statement.
471 * It really only needs to parse good SQL, or at least that's the theory...
472 * End-of-statement is assumed to be an unquoted, un commented semi-colon.
475 /* fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data); */
477 for(pos=0; pos < (eos - qry); pos++)
479 appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
480 /* fprintf(stderr, " %c",qry[pos]); */
482 switch (AH->sqlparse.state) {
484 case SQL_SCAN: /* Default state == 0, set in _allocAH */
488 /* Send It & reset the buffer */
489 /* fprintf(stderr, " sending: '%s'\n\n", AH->sqlBuf->data); */
490 ExecuteSqlCommand(AH, AH->sqlBuf, "Could not execute query");
491 resetPQExpBuffer(AH->sqlBuf);
492 AH->sqlparse.lastChar = '\0';
496 if (qry[pos] == '"' || qry[pos] == '\'')
498 /* fprintf(stderr,"[startquote]\n"); */
499 AH->sqlparse.state = SQL_IN_QUOTE;
500 AH->sqlparse.quoteChar = qry[pos];
501 AH->sqlparse.backSlash = 0;
503 else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
505 AH->sqlparse.state = SQL_IN_SQL_COMMENT;
507 else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
509 AH->sqlparse.state = SQL_IN_EXT_COMMENT;
511 AH->sqlparse.lastChar = qry[pos];
516 case SQL_IN_SQL_COMMENT:
518 if (qry[pos] == '\n')
519 AH->sqlparse.state = SQL_SCAN;
522 case SQL_IN_EXT_COMMENT:
524 if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
525 AH->sqlparse.state = SQL_SCAN;
530 if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
532 /* fprintf(stderr,"[endquote]\n"); */
533 AH->sqlparse.state = SQL_SCAN;
538 if (qry[pos] == '\\')
540 if (AH->sqlparse.lastChar == '\\')
541 AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
543 AH->sqlparse.backSlash = 1;
545 AH->sqlparse.backSlash = 0;
551 AH->sqlparse.lastChar = qry[pos];
552 /* fprintf(stderr, "\n"); */
560 void FixupBlobRefs(ArchiveHandle *AH, char *tablename)
562 PQExpBuffer tblQry = createPQExpBuffer();
563 PGresult *res, *uRes;
567 for(i=0 ; i < strlen(tablename) ; i++)
568 tablename[i] = tolower(tablename[i]);
570 if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
573 appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
574 " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
575 " AND t.typname = 'oid' AND c.relname = '%s';", tablename);
577 res = PQexec(AH->blobConnection, tblQry->data);
579 die_horribly(AH, "%s: could not find OID attrs of %s. Explanation from backend '%s'\n",
580 progname, tablename, PQerrorMessage(AH->connection));
582 if ((n = PQntuples(res)) == 0) {
584 ahlog(AH, 1, "No OID attributes in table %s\n", tablename);
589 for (i = 0 ; i < n ; i++)
591 attr = PQgetvalue(res, i, 0);
593 ahlog(AH, 1, " - %s.%s\n", tablename, attr);
595 resetPQExpBuffer(tblQry);
596 appendPQExpBuffer(tblQry, "Update \"%s\" Set \"%s\" = x.newOid From %s x "
597 "Where x.oldOid = \"%s\".\"%s\";",
599 tablename, attr, BLOB_XREF_TABLE, tablename, attr);
601 ahlog(AH, 10, " - sql = %s\n", tblQry->data);
603 uRes = PQexec(AH->blobConnection, tblQry->data);
605 die_horribly(AH, "%s: could not update attr %s of table %s. Explanation from backend '%s'\n",
606 progname, attr, tablename, PQerrorMessage(AH->connection));
608 if ( PQresultStatus(uRes) != PGRES_COMMAND_OK )
609 die_horribly(AH, "%s: error while updating attr %s of table %s. Explanation from backend '%s'\n",
610 progname, attr, tablename, PQerrorMessage(AH->connection));
620 * Convenient SQL calls
622 void CreateBlobXrefTable(ArchiveHandle* AH)
624 PQExpBuffer qry = createPQExpBuffer();
626 /* IF we don't have a BLOB connection, then create one */
627 if (!AH->blobConnection)
629 AH->blobConnection = _connectDB(AH, NULL, NULL);
632 ahlog(AH, 1, "Creating table for BLOBS xrefs\n");
634 appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
636 _executeSqlCommand(AH, AH->blobConnection, qry, "can not create BLOB xref table '" BLOB_XREF_TABLE "'");
638 resetPQExpBuffer(qry);
640 appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
641 _executeSqlCommand(AH, AH->blobConnection, qry, "can not create index on BLOB xref table '" BLOB_XREF_TABLE "'");
644 void InsertBlobXref(ArchiveHandle* AH, int old, int new)
646 PQExpBuffer qry = createPQExpBuffer();
648 appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
650 _executeSqlCommand(AH, AH->blobConnection, qry, "can not create BLOB xref entry");
653 void StartTransaction(ArchiveHandle* AH)
655 PQExpBuffer qry = createPQExpBuffer();
657 appendPQExpBuffer(qry, "Begin;");
659 ExecuteSqlCommand(AH, qry, "can not start database transaction");
662 void CommitTransaction(ArchiveHandle* AH)
664 PQExpBuffer qry = createPQExpBuffer();
666 appendPQExpBuffer(qry, "Commit;");
668 ExecuteSqlCommand(AH, qry, "can not commit database transaction");