]> granicus.if.org Git - postgresql/blob - src/bin/pg_dump/pg_backup_db.c
Avoid crashing pg_dump if we can't connect to the database server, and
[postgresql] / src / bin / pg_dump / pg_backup_db.c
1 /*-------------------------------------------------------------------------
2  *
3  * pg_backup_db.c
4  *
5  *      Implements the basic DB functions used by the archiver.
6  *
7  * IDENTIFICATION
8  *        $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.64 2005/07/27 05:14:12 neilc Exp $
9  *
10  *-------------------------------------------------------------------------
11  */
12
13 #include "pg_backup.h"
14 #include "pg_backup_archiver.h"
15 #include "pg_backup_db.h"
16 #include "dumputils.h"
17
18 #include <unistd.h>
19 #include <ctype.h>
20
21 #ifdef HAVE_TERMIOS_H
22 #include <termios.h>
23 #endif
24
25 #include "libpq-fe.h"
26 #include "libpq/libpq-fs.h"
27 #ifndef HAVE_STRDUP
28 #include "strdup.h"
29 #endif
30
31 static const char *modulename = gettext_noop("archiver (db)");
32
33 static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
34 static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser);
35 static void notice_processor(void *arg, const char *message);
36 static char *_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos);
37 static char *_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos);
38
39 static int      _isIdentChar(unsigned char c);
40 static int      _isDQChar(unsigned char c, int atStart);
41
42 #define DB_MAX_ERR_STMT 128
43
44 static int
45 _parse_version(ArchiveHandle *AH, const char *versionString)
46 {
47         int                     v;
48
49         v = parse_version(versionString);
50         if (v < 0)
51                 die_horribly(AH, modulename, "could not parse version string \"%s\"\n", versionString);
52
53         return v;
54 }
55
56 static void
57 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
58 {
59         int                     myversion;
60         const char *remoteversion_str;
61         int                     remoteversion;
62
63         myversion = _parse_version(AH, PG_VERSION);
64
65         remoteversion_str = PQparameterStatus(AH->connection, "server_version");
66         if (!remoteversion_str)
67                 die_horribly(AH, modulename, "could not get server_version from libpq\n");
68
69         remoteversion = _parse_version(AH, remoteversion_str);
70
71         AH->public.remoteVersionStr = strdup(remoteversion_str);
72         AH->public.remoteVersion = remoteversion;
73
74         if (myversion != remoteversion
75                 && (remoteversion < AH->public.minRemoteVersion ||
76                         remoteversion > AH->public.maxRemoteVersion))
77         {
78                 write_msg(NULL, "server version: %s; %s version: %s\n",
79                                   remoteversion_str, progname, PG_VERSION);
80                 if (ignoreVersion)
81                         write_msg(NULL, "proceeding despite version mismatch\n");
82                 else
83                         die_horribly(AH, NULL, "aborting because of version mismatch  (Use the -i option to proceed anyway.)\n");
84         }
85 }
86
87 /*
88  * Reconnect to the server.  If dbname is not NULL, use that database,
89  * else the one associated with the archive handle.  If username is
90  * not NULL, use that user name, else the one from the handle.  If
91  * both the database and the user match the existing connection already,
92  * nothing will be done.
93  *
94  * Returns 1 in any case.
95  */
96 int
97 ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
98 {
99         PGconn     *newConn;
100         const char *newdbname;
101         const char *newusername;
102
103         if (!dbname)
104                 newdbname = PQdb(AH->connection);
105         else
106                 newdbname = dbname;
107
108         if (!username)
109                 newusername = PQuser(AH->connection);
110         else
111                 newusername = username;
112
113         /* Let's see if the request is already satisfied */
114         if (strcmp(newdbname, PQdb(AH->connection)) == 0 &&
115                 strcmp(newusername, PQuser(AH->connection)) == 0)
116                 return 1;
117
118         newConn = _connectDB(AH, newdbname, newusername);
119
120         PQfinish(AH->connection);
121         AH->connection = newConn;
122
123         return 1;
124 }
125
126 /*
127  * Connect to the db again.
128  */
129 static PGconn *
130 _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
131 {
132         int                     need_pass;
133         PGconn     *newConn;
134         char       *password = NULL;
135         int                     badPwd = 0;
136         int                     noPwd = 0;
137         char       *newdb;
138         char       *newuser;
139
140         if (!reqdb)
141                 newdb = PQdb(AH->connection);
142         else
143                 newdb = (char *) reqdb;
144
145         if (!requser || (strlen(requser) == 0))
146                 newuser = PQuser(AH->connection);
147         else
148                 newuser = (char *) requser;
149
150         ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n", newdb, newuser);
151
152         if (AH->requirePassword)
153         {
154                 password = simple_prompt("Password: ", 100, false);
155                 if (password == NULL)
156                         die_horribly(AH, modulename, "out of memory\n");
157         }
158
159         do
160         {
161                 need_pass = false;
162                 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
163                                                            NULL, NULL, newdb,
164                                                            newuser, password);
165                 if (!newConn)
166                         die_horribly(AH, modulename, "failed to reconnect to database\n");
167
168                 if (PQstatus(newConn) == CONNECTION_BAD)
169                 {
170                         noPwd = (strcmp(PQerrorMessage(newConn),
171                                                         PQnoPasswordSupplied) == 0);
172                         badPwd = (strncmp(PQerrorMessage(newConn),
173                                         "Password authentication failed for user", 39) == 0);
174
175                         if (noPwd || badPwd)
176                         {
177                                 if (badPwd)
178                                         fprintf(stderr, "Password incorrect\n");
179
180                                 fprintf(stderr, "Connecting to %s as %s\n",
181                                                 newdb, newuser);
182
183                                 need_pass = true;
184                                 if (password)
185                                         free(password);
186                                 password = simple_prompt("Password: ", 100, false);
187                         }
188                         else
189                                 die_horribly(AH, modulename, "could not reconnect to database: %s",
190                                                          PQerrorMessage(newConn));
191                         PQfinish(newConn);
192                 }
193         } while (need_pass);
194
195         if (password)
196                 free(password);
197
198         /* check for version mismatch */
199         _check_database_version(AH, true);
200
201         PQsetNoticeProcessor(newConn, notice_processor, NULL);
202
203         return newConn;
204 }
205
206
207 /*
208  * Make a database connection with the given parameters.  The
209  * connection handle is returned, the parameters are stored in AHX.
210  * An interactive password prompt is automatically issued if required.
211  */
212 PGconn *
213 ConnectDatabase(Archive *AHX,
214                                 const char *dbname,
215                                 const char *pghost,
216                                 const char *pgport,
217                                 const char *username,
218                                 const int reqPwd,
219                                 const int ignoreVersion)
220 {
221         ArchiveHandle *AH = (ArchiveHandle *) AHX;
222         char       *password = NULL;
223         bool            need_pass = false;
224
225         if (AH->connection)
226                 die_horribly(AH, modulename, "already connected to a database\n");
227
228         if (reqPwd)
229         {
230                 password = simple_prompt("Password: ", 100, false);
231                 if (password == NULL)
232                         die_horribly(AH, modulename, "out of memory\n");
233                 AH->requirePassword = true;
234         }
235         else
236                 AH->requirePassword = false;
237
238         /*
239          * Start the connection.  Loop until we have a password if requested
240          * by backend.
241          */
242         do
243         {
244                 need_pass = false;
245                 AH->connection = PQsetdbLogin(pghost, pgport, NULL, NULL,
246                                                                           dbname, username, password);
247
248                 if (!AH->connection)
249                         die_horribly(AH, modulename, "failed to connect to database\n");
250
251                 if (PQstatus(AH->connection) == CONNECTION_BAD &&
252                         strcmp(PQerrorMessage(AH->connection), PQnoPasswordSupplied) == 0 &&
253                         !feof(stdin))
254                 {
255                         PQfinish(AH->connection);
256                         need_pass = true;
257                         free(password);
258                         password = NULL;
259                         password = simple_prompt("Password: ", 100, false);
260                 }
261         } while (need_pass);
262
263         if (password)
264                 free(password);
265
266         /* check to see that the backend connection was successfully made */
267         if (PQstatus(AH->connection) == CONNECTION_BAD)
268                 die_horribly(AH, modulename, "connection to database \"%s\" failed: %s",
269                                          PQdb(AH->connection), PQerrorMessage(AH->connection));
270
271         /* check for version mismatch */
272         _check_database_version(AH, ignoreVersion);
273
274         PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
275
276         return AH->connection;
277 }
278
279
280 static void
281 notice_processor(void *arg, const char *message)
282 {
283         write_msg(NULL, "%s", message);
284 }
285
286
287 /* Public interface */
288 /* Convenience function to send a query. Monitors result to handle COPY statements */
289 int
290 ExecuteSqlCommand(ArchiveHandle *AH, PQExpBuffer qry, char *desc)
291 {
292         PGconn     *conn = AH->connection;
293         PGresult   *res;
294         char            errStmt[DB_MAX_ERR_STMT];
295
296         /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
297         res = PQexec(conn, qry->data);
298         if (!res)
299                 die_horribly(AH, modulename, "%s: no result from server\n", desc);
300
301         if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
302         {
303                 if (PQresultStatus(res) == PGRES_COPY_IN)
304                 {
305                         AH->pgCopyIn = 1;
306                 }
307                 else
308                 {
309                         strncpy(errStmt, qry->data, DB_MAX_ERR_STMT);
310                         if (errStmt[DB_MAX_ERR_STMT - 1] != '\0')
311                         {
312                                 errStmt[DB_MAX_ERR_STMT - 4] = '.';
313                                 errStmt[DB_MAX_ERR_STMT - 3] = '.';
314                                 errStmt[DB_MAX_ERR_STMT - 2] = '.';
315                                 errStmt[DB_MAX_ERR_STMT - 1] = '\0';
316                         }
317                         warn_or_die_horribly(AH, modulename, "%s: %s    Command was: %s\n",
318                                                                  desc, PQerrorMessage(AH->connection),
319                                                                  errStmt);
320                 }
321         }
322
323         PQclear(res);
324
325         return strlen(qry->data);
326 }
327
328 /*
329  * Used by ExecuteSqlCommandBuf to send one buffered line when running a COPY command.
330  */
331 static char *
332 _sendCopyLine(ArchiveHandle *AH, char *qry, char *eos)
333 {
334         size_t          loc;                    /* Location of next newline */
335         int                     pos = 0;                /* Current position */
336         int                     sPos = 0;               /* Last pos of a slash char */
337         int                     isEnd = 0;
338
339         /* loop to find unquoted newline ending the line of COPY data */
340         for (;;)
341         {
342                 loc = strcspn(&qry[pos], "\n") + pos;
343
344                 /* If no match, then wait */
345                 if (loc >= (eos - qry)) /* None found */
346                 {
347                         appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
348                         return eos;
349                 }
350
351                 /*
352                  * fprintf(stderr, "Found cr at %d, prev char was %c, next was
353                  * %c\n", loc, qry[loc-1], qry[loc+1]);
354                  */
355
356                 /* Count the number of preceding slashes */
357                 sPos = loc;
358                 while (sPos > 0 && qry[sPos - 1] == '\\')
359                         sPos--;
360
361                 sPos = loc - sPos;
362
363                 /*
364                  * If an odd number of preceding slashes, then \n was escaped so
365                  * set the next search pos, and loop (if any left).
366                  */
367                 if ((sPos & 1) == 1)
368                 {
369                         /* fprintf(stderr, "cr was escaped\n"); */
370                         pos = loc + 1;
371                         if (pos >= (eos - qry))
372                         {
373                                 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
374                                 return eos;
375                         }
376                 }
377                 else
378                         break;
379         }
380
381         /* We found an unquoted newline */
382         qry[loc] = '\0';
383         appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
384         isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
385
386         /*---------
387          * fprintf(stderr, "Sending '%s' via
388          *              COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd);
389          *---------
390          */
391
392         if (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
393                 die_horribly(AH, modulename, "error returned by PQputline\n");
394
395         resetPQExpBuffer(AH->pgCopyBuf);
396
397         /*
398          * fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data);
399          */
400
401         if (isEnd)
402         {
403                 if (PQendcopy(AH->connection) != 0)
404                         die_horribly(AH, modulename, "error returned by PQendcopy\n");
405
406                 AH->pgCopyIn = 0;
407         }
408
409         return qry + loc + 1;
410 }
411
412 /*
413  * Used by ExecuteSqlCommandBuf to send one buffered line of SQL (not data for the copy command).
414  */
415 static char *
416 _sendSQLLine(ArchiveHandle *AH, char *qry, char *eos)
417 {
418         int                     pos = 0;                /* Current position */
419         char       *sqlPtr;
420         int                     consumed;
421         int                     startDT = 0;
422
423         /*
424          * The following is a mini state machine to assess the end of an SQL
425          * statement. It really only needs to parse good SQL, or at least
426          * that's the theory... End-of-statement is assumed to be an unquoted,
427          * un commented semi-colon.
428          */
429
430         /*
431          * fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data);
432          */
433
434         for (pos = 0; pos < (eos - qry); pos++)
435         {
436                 appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
437                 /* fprintf(stderr, " %c",qry[pos]); */
438
439                 /* Loop until character consumed */
440                 do
441                 {
442                         /*
443                          * If a character needs to be scanned in a different state,
444                          * consumed can be set to 0 to avoid advancing. Care must be
445                          * taken to ensure internal state is not damaged.
446                          */
447                         consumed = 1;
448
449                         switch (AH->sqlparse.state)
450                         {
451
452                                 case SQL_SCAN:  /* Default state == 0, set in _allocAH */
453                                         if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
454                                         {
455                                                 /*
456                                                  * We've got the end of a statement. Send It &
457                                                  * reset the buffer.
458                                                  */
459
460                                                 /*
461                                                  * fprintf(stderr, "    sending: '%s'\n\n",
462                                                  * AH->sqlBuf->data);
463                                                  */
464                                                 ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query");
465                                                 resetPQExpBuffer(AH->sqlBuf);
466                                                 AH->sqlparse.lastChar = '\0';
467
468                                                 /*
469                                                  * Remove any following newlines - so that
470                                                  * embedded COPY commands don't get a starting
471                                                  * newline.
472                                                  */
473                                                 pos++;
474                                                 for (; pos < (eos - qry) && qry[pos] == '\n'; pos++);
475
476                                                 /* We've got our line, so exit */
477                                                 return qry + pos;
478                                         }
479                                         else
480                                         {
481                                                 /*
482                                                  * Look for normal boring quote chars, or
483                                                  * dollar-quotes. We make the assumption that
484                                                  * $-quotes will not have an ident character
485                                                  * before them in all pg_dump output.
486                                                  */
487                                                 if (qry[pos] == '"'
488                                                         || qry[pos] == '\''
489                                                         || (qry[pos] == '$' && _isIdentChar(AH->sqlparse.lastChar) == 0)
490                                                         )
491                                                 {
492                                                         /* fprintf(stderr,"[startquote]\n"); */
493                                                         AH->sqlparse.state = SQL_IN_QUOTE;
494                                                         AH->sqlparse.quoteChar = qry[pos];
495                                                         AH->sqlparse.backSlash = 0;
496                                                         if (qry[pos] == '$')
497                                                         {
498                                                                 /* override the state */
499                                                                 AH->sqlparse.state = SQL_IN_DOLLARTAG;
500                                                                 /* Used for checking first char of tag */
501                                                                 startDT = 1;
502                                                                 /* We store the tag for later comparison. */
503                                                                 AH->sqlparse.tagBuf = createPQExpBuffer();
504                                                                 /* Get leading $ */
505                                                                 appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
506                                                         }
507                                                 }
508                                                 else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
509                                                         AH->sqlparse.state = SQL_IN_SQL_COMMENT;
510                                                 else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
511                                                         AH->sqlparse.state = SQL_IN_EXT_COMMENT;
512                                                 else if (qry[pos] == '(')
513                                                         AH->sqlparse.braceDepth++;
514                                                 else if (qry[pos] == ')')
515                                                         AH->sqlparse.braceDepth--;
516
517                                                 AH->sqlparse.lastChar = qry[pos];
518                                         }
519                                         break;
520
521                                 case SQL_IN_DOLLARTAG:
522
523                                         /*
524                                          * Like a quote, we look for a closing char *but* we
525                                          * only allow a very limited set of contained chars,
526                                          * and no escape chars. If invalid chars are found, we
527                                          * abort tag processing.
528                                          */
529
530                                         if (qry[pos] == '$')
531                                         {
532                                                 /* fprintf(stderr,"[endquote]\n"); */
533                                                 /* Get trailing $ */
534                                                 appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
535                                                 AH->sqlparse.state = SQL_IN_DOLLARQUOTE;
536                                         }
537                                         else
538                                         {
539                                                 if (_isDQChar(qry[pos], startDT))
540                                                 {
541                                                         /* Valid, so add */
542                                                         appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
543                                                 }
544                                                 else
545                                                 {
546                                                         /*
547                                                          * Jump back to 'scan' state, we're not really
548                                                          * in a tag, and valid tag chars do not
549                                                          * include the various chars we look for in
550                                                          * this state machine, so it's safe to just
551                                                          * jump from this state back to SCAN. We set
552                                                          * consumed = 0 so that this char gets
553                                                          * rescanned in new state.
554                                                          */
555                                                         destroyPQExpBuffer(AH->sqlparse.tagBuf);
556                                                         AH->sqlparse.state = SQL_SCAN;
557                                                         consumed = 0;
558                                                 }
559                                         }
560                                         startDT = 0;
561                                         break;
562
563
564                                 case SQL_IN_DOLLARQUOTE:
565
566                                         /*
567                                          * Comparing the entire string backwards each time is
568                                          * NOT efficient, but dollar quotes in pg_dump are
569                                          * small and the code is a lot simpler.
570                                          */
571                                         sqlPtr = AH->sqlBuf->data + AH->sqlBuf->len - AH->sqlparse.tagBuf->len;
572
573                                         if (strncmp(AH->sqlparse.tagBuf->data, sqlPtr, AH->sqlparse.tagBuf->len) == 0)
574                                         {
575                                                 /* End of $-quote */
576                                                 AH->sqlparse.state = SQL_SCAN;
577                                                 destroyPQExpBuffer(AH->sqlparse.tagBuf);
578                                         }
579                                         break;
580
581                                 case SQL_IN_SQL_COMMENT:
582                                         if (qry[pos] == '\n')
583                                                 AH->sqlparse.state = SQL_SCAN;
584                                         break;
585
586                                 case SQL_IN_EXT_COMMENT:
587                                         if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
588                                                 AH->sqlparse.state = SQL_SCAN;
589                                         break;
590
591                                 case SQL_IN_QUOTE:
592
593                                         if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
594                                         {
595                                                 /* fprintf(stderr,"[endquote]\n"); */
596                                                 AH->sqlparse.state = SQL_SCAN;
597                                         }
598                                         else
599                                         {
600                                                 if (qry[pos] == '\\')
601                                                 {
602                                                         if (AH->sqlparse.lastChar == '\\')
603                                                                 AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
604                                                         else
605                                                                 AH->sqlparse.backSlash = 1;
606                                                 }
607                                                 else
608                                                         AH->sqlparse.backSlash = 0;
609                                         }
610                                         break;
611
612                         }
613
614                 } while (consumed == 0);
615
616                 AH->sqlparse.lastChar = qry[pos];
617                 /* fprintf(stderr, "\n"); */
618         }
619
620         /*
621          * If we get here, we've processed entire string with no complete SQL
622          * stmt
623          */
624         return eos;
625 }
626
627
628 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
629 int
630 ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, size_t bufLen)
631 {
632         char       *qry = (char *) qryv;
633         char       *eos = qry + bufLen;
634
635         /*
636          * fprintf(stderr, "\n\n*****\n
637          * Buffer:\n\n%s\n*******************\n\n", qry);
638          */
639
640         /* Could switch between command and COPY IN mode at each line */
641         while (qry < eos)
642         {
643                 if (AH->pgCopyIn)
644                         qry = _sendCopyLine(AH, qry, eos);
645                 else
646                         qry = _sendSQLLine(AH, qry, eos);
647         }
648
649         return 1;
650 }
651
652 void
653 StartTransaction(ArchiveHandle *AH)
654 {
655         PQExpBuffer qry = createPQExpBuffer();
656
657         appendPQExpBuffer(qry, "BEGIN");
658
659         ExecuteSqlCommand(AH, qry, "could not start database transaction");
660
661         destroyPQExpBuffer(qry);
662 }
663
664 void
665 CommitTransaction(ArchiveHandle *AH)
666 {
667         PQExpBuffer qry = createPQExpBuffer();
668
669         appendPQExpBuffer(qry, "COMMIT");
670
671         ExecuteSqlCommand(AH, qry, "could not commit database transaction");
672
673         destroyPQExpBuffer(qry);
674 }
675
676 static int
677 _isIdentChar(unsigned char c)
678 {
679         if ((c >= 'a' && c <= 'z')
680                 || (c >= 'A' && c <= 'Z')
681                 || (c >= '0' && c <= '9')
682                 || (c == '_')
683                 || (c == '$')
684                 || (c >= (unsigned char) '\200')                /* no need to check <=
685                                                                                                  * \377 */
686                 )
687                 return 1;
688         else
689                 return 0;
690 }
691
692 static int
693 _isDQChar(unsigned char c, int atStart)
694 {
695         if ((c >= 'a' && c <= 'z')
696                 || (c >= 'A' && c <= 'Z')
697                 || (c == '_')
698                 || (atStart == 0 && c >= '0' && c <= '9')
699                 || (c >= (unsigned char) '\200')                /* no need to check <=
700                                                                                                  * \377 */
701                 )
702                 return 1;
703         else
704                 return 0;
705 }