/*------------------------------------------------------------------------- * * pg_dumpall * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.41 2004/06/10 16:35:17 momjian Exp $ * *------------------------------------------------------------------------- */ #include "postgres_fe.h" #include #ifdef ENABLE_NLS #include #endif #ifndef HAVE_STRDUP #include "strdup.h" #endif #include #include #include "getopt_long.h" #ifndef HAVE_OPTRESET int optreset; #endif #include "dumputils.h" #include "libpq-fe.h" #include "pg_backup.h" #include "pqexpbuffer.h" #define _(x) gettext((x)) /* version string we expect back from postgres */ #define PG_VERSIONSTR "pg_dump (PostgreSQL) " PG_VERSION "\n" static const char *progname; static void help(void); static void dumpUsers(PGconn *conn); static void dumpGroups(PGconn *conn); static void dumpCreateDB(PGconn *conn); static void dumpDatabaseConfig(PGconn *conn, const char *dbname); static void dumpUserConfig(PGconn *conn, const char *username); static void makeAlterConfigCommand(const char *arrayitem, const char *type, const char *name); static void dumpDatabases(PGconn *conn); static void dumpTimestamp(char *msg); static int runPgDump(const char *dbname); static PGconn *connectDatabase(const char *dbname, const char *pghost, const char *pgport, const char *pguser, bool require_password); static PGresult *executeQuery(PGconn *conn, const char *query); char pg_dump_bin[MAXPGPATH]; PQExpBuffer pgdumpopts; bool output_clean = false; bool skip_acls = false; bool verbose = false; int server_version; int main(int argc, char *argv[]) { char *pghost = NULL; char *pgport = NULL; char *pguser = NULL; bool force_password = false; bool data_only = false; bool globals_only = false; bool schema_only = false; PGconn *conn; int c, ret; static struct option long_options[] = { {"data-only", no_argument, NULL, 'a'}, {"clean", no_argument, NULL, 'c'}, {"inserts", no_argument, NULL, 'd'}, {"attribute-inserts", no_argument, NULL, 'D'}, {"column-inserts", no_argument, NULL, 'D'}, {"globals-only", no_argument, NULL, 'g'}, {"host", required_argument, NULL, 'h'}, {"ignore-version", no_argument, NULL, 'i'}, {"oids", no_argument, NULL, 'o'}, {"port", required_argument, NULL, 'p'}, {"password", no_argument, NULL, 'W'}, {"schema-only", no_argument, NULL, 's'}, {"username", required_argument, NULL, 'U'}, {"verbose", no_argument, NULL, 'v'}, {"no-privileges", no_argument, NULL, 'x'}, {"no-acl", no_argument, NULL, 'x'}, {NULL, 0, NULL, 0} }; int optindex; set_pglocale_pgservice(argv[0], "pg_dump"); progname = get_progname(argv[0]); if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { help(); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { puts("pg_dumpall (PostgreSQL) " PG_VERSION); exit(0); } } if ((ret = find_other_exec(argv[0], "pg_dump", PG_VERSIONSTR, pg_dump_bin)) < 0) { if (ret == -1) fprintf(stderr, _("The program \"pg_dump\" is needed by %s " "but was not found in the same directory as \"%s\".\n" "Check your installation.\n"), progname, progname); else fprintf(stderr, _("The program \"pg_dump\" was found by %s " "but was not the same version as \"%s\".\n" "Check your installation.\n"), progname, progname); exit(1); } pgdumpopts = createPQExpBuffer(); while ((c = getopt_long(argc, argv, "acdDgh:iop:sU:vWx", long_options, &optindex)) != -1) { switch (c) { case 'a': data_only = true; appendPQExpBuffer(pgdumpopts, " -a"); break; case 'c': output_clean = true; break; case 'd': case 'D': appendPQExpBuffer(pgdumpopts, " -%c", c); break; case 'g': globals_only = true; break; case 'h': pghost = optarg; appendPQExpBuffer(pgdumpopts, " -h '%s'", pghost); break; case 'i': case 'o': appendPQExpBuffer(pgdumpopts, " -%c", c); break; case 'p': pgport = optarg; appendPQExpBuffer(pgdumpopts, " -p '%s'", pgport); break; case 's': schema_only = true; appendPQExpBuffer(pgdumpopts, " -s"); break; case 'U': pguser = optarg; appendPQExpBuffer(pgdumpopts, " -U '%s'", pguser); break; case 'v': verbose = true; appendPQExpBuffer(pgdumpopts, " -v"); break; case 'W': force_password = true; appendPQExpBuffer(pgdumpopts, " -W"); break; case 'x': skip_acls = true; appendPQExpBuffer(pgdumpopts, " -x"); break; default: fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); exit(1); } } if (optind < argc) { fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"), progname, argv[optind]); fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); exit(1); } conn = connectDatabase("template1", pghost, pgport, pguser, force_password); printf("--\n-- PostgreSQL database cluster dump\n--\n\n"); if (verbose) dumpTimestamp("Started on"); printf("\\connect \"template1\"\n\n"); if (!data_only) { dumpUsers(conn); dumpGroups(conn); } if (!globals_only) { if (!data_only) dumpCreateDB(conn); dumpDatabases(conn); } PQfinish(conn); if (verbose) dumpTimestamp("Completed on"); printf("--\n-- PostgreSQL database cluster dump complete\n--\n\n"); exit(0); } static void help(void) { printf(_("%s extracts a PostgreSQL database cluster into an SQL script file.\n\n"), progname); printf(_("Usage:\n")); printf(_(" %s [OPTION]...\n"), progname); printf(_("\nOptions:\n")); printf(_(" -a, --data-only dump only the data, not the schema\n")); printf(_(" -c, --clean clean (drop) databases prior to create\n")); printf(_(" -d, --inserts dump data as INSERT, rather than COPY, commands\n")); printf(_(" -D, --column-inserts dump data as INSERT commands with column names\n")); printf(_(" -g, --globals-only dump only global objects, no databases\n")); printf(_(" -i, --ignore-version proceed even when server version mismatches\n" " pg_dumpall version\n")); printf(_(" -s, --schema-only dump only the schema, no data\n")); printf(_(" -o, --oids include OIDs in dump\n")); printf(_(" -v, --verbose verbose mode\n")); printf(_(" -x, --no-privileges do not dump privileges (grant/revoke)\n")); printf(_(" --help show this help, then exit\n")); printf(_(" --version output version information, then exit\n")); printf(_("\nConnection options:\n")); printf(_(" -h, --host=HOSTNAME database server host or socket directory\n")); printf(_(" -p, --port=PORT database server port number\n")); printf(_(" -U, --username=NAME connect as specified database user\n")); printf(_(" -W, --password force password prompt (should happen automatically)\n")); printf(_("\nThe SQL script will be written to the standard output.\n\n")); printf(_("Report bugs to .\n")); } /* * Dump users (but not the user created by initdb). */ static void dumpUsers(PGconn *conn) { PGresult *res; int i; printf("--\n-- Users\n--\n\n"); printf("DELETE FROM pg_shadow WHERE usesysid <> (SELECT datdba FROM pg_database WHERE datname = 'template0');\n\n"); if (server_version >= 70100) res = executeQuery(conn, "SELECT usename, usesysid, passwd, usecreatedb, " "usesuper, valuntil " "FROM pg_shadow " "WHERE usesysid <> (SELECT datdba FROM pg_database WHERE datname = 'template0')"); else res = executeQuery(conn, "SELECT usename, usesysid, passwd, usecreatedb, " "usesuper, valuntil " "FROM pg_shadow " "WHERE usesysid <> (SELECT datdba FROM pg_database WHERE datname = 'template1')"); for (i = 0; i < PQntuples(res); i++) { PQExpBuffer buf = createPQExpBuffer(); const char *username; username = PQgetvalue(res, i, 0); appendPQExpBuffer(buf, "CREATE USER %s WITH SYSID %s", fmtId(username), PQgetvalue(res, i, 1)); if (!PQgetisnull(res, i, 2)) { appendPQExpBuffer(buf, " PASSWORD "); appendStringLiteral(buf, PQgetvalue(res, i, 2), true); } if (strcmp(PQgetvalue(res, i, 3), "t") == 0) appendPQExpBuffer(buf, " CREATEDB"); else appendPQExpBuffer(buf, " NOCREATEDB"); if (strcmp(PQgetvalue(res, i, 4), "t") == 0) appendPQExpBuffer(buf, " CREATEUSER"); else appendPQExpBuffer(buf, " NOCREATEUSER"); if (!PQgetisnull(res, i, 5)) appendPQExpBuffer(buf, " VALID UNTIL '%s'", PQgetvalue(res, i, 5)); appendPQExpBuffer(buf, ";\n"); printf("%s", buf->data); destroyPQExpBuffer(buf); if (server_version >= 70300) dumpUserConfig(conn, username); } PQclear(res); printf("\n\n"); } /* * Dump groups. */ static void dumpGroups(PGconn *conn) { PGresult *res; int i; printf("--\n-- Groups\n--\n\n"); printf("DELETE FROM pg_group;\n\n"); res = executeQuery(conn, "SELECT groname, grosysid, grolist FROM pg_group"); for (i = 0; i < PQntuples(res); i++) { PQExpBuffer buf = createPQExpBuffer(); char *val; char *tok; appendPQExpBuffer(buf, "CREATE GROUP %s WITH SYSID %s;\n", fmtId(PQgetvalue(res, i, 0)), PQgetvalue(res, i, 1)); val = strdup(PQgetvalue(res, i, 2)); tok = strtok(val, ",{}"); while (tok) { PGresult *res2; PQExpBuffer buf2 = createPQExpBuffer(); int j; appendPQExpBuffer(buf2, "SELECT usename FROM pg_shadow WHERE usesysid = %s;", tok); res2 = executeQuery(conn, buf2->data); destroyPQExpBuffer(buf2); for (j = 0; j < PQntuples(res2); j++) { appendPQExpBuffer(buf, "ALTER GROUP %s ", fmtId(PQgetvalue(res, i, 0))); appendPQExpBuffer(buf, "ADD USER %s;\n", fmtId(PQgetvalue(res2, j, 0))); } PQclear(res2); tok = strtok(NULL, "{},"); } free(val); printf("%s", buf->data); destroyPQExpBuffer(buf); } PQclear(res); printf("\n\n"); } /* * Dump commands to create each database. * * To minimize the number of reconnections (and possibly ensuing * password prompts) required by the output script, we emit all CREATE * DATABASE commands during the initial phase of the script, and then * run pg_dump for each database to dump the contents of that * database. We skip databases marked not datallowconn, since we'd be * unable to connect to them anyway (and besides, we don't want to * dump template0). */ static void dumpCreateDB(PGconn *conn) { PGresult *res; int i; printf("--\n-- Database creation\n--\n\n"); if (server_version >= 70300) res = executeQuery(conn, "SELECT datname, " "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "datistemplate, datpath, datacl " "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) " "WHERE datallowconn ORDER BY 1"); else if (server_version >= 70100) res = executeQuery(conn, "SELECT datname, " "coalesce(" "(select usename from pg_shadow where usesysid=datdba), " "(select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), " "pg_encoding_to_char(d.encoding), " "datistemplate, datpath, '' as datacl " "FROM pg_database d " "WHERE datallowconn ORDER BY 1"); else { /* * In 7.0, datpath is either the same as datname, or the user-given * location with "/" and the datname appended. We must strip this * junk off to produce a correct LOCATION value. * * Note: 7.0 fails to cope with sub-select in COALESCE, so just * deal with getting a NULL by not printing any OWNER clause. */ res = executeQuery(conn, "SELECT datname, " "(select usename from pg_shadow where usesysid=datdba), " "pg_encoding_to_char(d.encoding), " "'f' as datistemplate, " "CASE WHEN length(datpath) > length(datname) THEN " "substr(datpath,1,length(datpath)-length(datname)-1) " "ELSE '' END as datpath, " "'' as datacl " "FROM pg_database d " "ORDER BY 1"); } for (i = 0; i < PQntuples(res); i++) { PQExpBuffer buf; char *dbname = PQgetvalue(res, i, 0); char *dbowner = PQgetvalue(res, i, 1); char *dbencoding = PQgetvalue(res, i, 2); char *dbistemplate = PQgetvalue(res, i, 3); char *dbpath = PQgetvalue(res, i, 4); char *dbacl = PQgetvalue(res, i, 5); char *fdbname; if (strcmp(dbname, "template1") == 0) continue; buf = createPQExpBuffer(); /* needed for buildACLCommands() */ fdbname = strdup(fmtId(dbname)); if (output_clean) appendPQExpBuffer(buf, "DROP DATABASE %s;\n", fdbname); appendPQExpBuffer(buf, "CREATE DATABASE %s", fdbname); if (strlen(dbowner) != 0) appendPQExpBuffer(buf, " WITH OWNER = %s", fmtId(dbowner)); appendPQExpBuffer(buf, " TEMPLATE = template0"); if (strlen(dbpath) != 0) { appendPQExpBuffer(buf, " LOCATION = "); appendStringLiteral(buf, dbpath, true); } appendPQExpBuffer(buf, " ENCODING = "); appendStringLiteral(buf, dbencoding, true); appendPQExpBuffer(buf, ";\n"); if (strcmp(dbistemplate, "t") == 0) { appendPQExpBuffer(buf, "UPDATE pg_database SET datistemplate = 't' WHERE datname = "); appendStringLiteral(buf, dbname, true); appendPQExpBuffer(buf, ";\n"); } if (!skip_acls && !buildACLCommands(fdbname, "DATABASE", dbacl, dbowner, server_version, buf)) { fprintf(stderr, _("%s: could not parse ACL list (%s) for database \"%s\"\n"), progname, dbacl, fdbname); PQfinish(conn); exit(1); } printf("%s", buf->data); destroyPQExpBuffer(buf); free(fdbname); if (server_version >= 70300) dumpDatabaseConfig(conn, dbname); } PQclear(res); printf("\n\n"); } /* * Dump database-specific configuration */ static void dumpDatabaseConfig(PGconn *conn, const char *dbname) { PQExpBuffer buf = createPQExpBuffer(); int count = 1; for (;;) { PGresult *res; printfPQExpBuffer(buf, "SELECT datconfig[%d] FROM pg_database WHERE datname = ", count); appendStringLiteral(buf, dbname, true); appendPQExpBuffer(buf, ";"); res = executeQuery(conn, buf->data); if (!PQgetisnull(res, 0, 0)) { makeAlterConfigCommand(PQgetvalue(res, 0, 0), "DATABASE", dbname); PQclear(res); count++; } else { PQclear(res); break; } } destroyPQExpBuffer(buf); } /* * Dump user-specific configuration */ static void dumpUserConfig(PGconn *conn, const char *username) { PQExpBuffer buf = createPQExpBuffer(); int count = 1; for (;;) { PGresult *res; printfPQExpBuffer(buf, "SELECT useconfig[%d] FROM pg_shadow WHERE usename = ", count); appendStringLiteral(buf, username, true); appendPQExpBuffer(buf, ";"); res = executeQuery(conn, buf->data); if (!PQgetisnull(res, 0, 0)) { makeAlterConfigCommand(PQgetvalue(res, 0, 0), "USER", username); PQclear(res); count++; } else { PQclear(res); break; } } destroyPQExpBuffer(buf); } /* * Helper function for dumpXXXConfig(). */ static void makeAlterConfigCommand(const char *arrayitem, const char *type, const char *name) { char *pos; char *mine; PQExpBuffer buf = createPQExpBuffer(); mine = strdup(arrayitem); pos = strchr(mine, '='); if (pos == NULL) return; *pos = 0; appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name)); appendPQExpBuffer(buf, "SET %s TO ", fmtId(mine)); appendStringLiteral(buf, pos + 1, false); appendPQExpBuffer(buf, ";\n"); printf("%s", buf->data); destroyPQExpBuffer(buf); free(mine); } /* * Dump contents of databases. */ static void dumpDatabases(PGconn *conn) { PGresult *res; int i; if (server_version >= 70100) res = executeQuery(conn, "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1"); else res = executeQuery(conn, "SELECT datname FROM pg_database ORDER BY 1"); for (i = 0; i < PQntuples(res); i++) { int ret; char *dbname = PQgetvalue(res, i, 0); if (verbose) fprintf(stderr, _("%s: dumping database \"%s\"...\n"), progname, dbname); printf("\\connect %s\n\n", fmtId(dbname)); ret = runPgDump(dbname); if (ret != 0) { fprintf(stderr, _("%s: pg_dump failed on database \"%s\", exiting\n"), progname, dbname); exit(1); } } PQclear(res); } /* * Run pg_dump on dbname. */ static int runPgDump(const char *dbname) { PQExpBuffer cmd = createPQExpBuffer(); const char *p; int ret; appendPQExpBuffer(cmd, "%s\"%s\" %s -Fp '", SYSTEMQUOTE, pg_dump_bin, pgdumpopts->data); /* Shell quoting is not quite like SQL quoting, so can't use fmtId */ for (p = dbname; *p; p++) { if (*p == '\'') appendPQExpBuffer(cmd, "'\"'\"'"); else appendPQExpBufferChar(cmd, *p); } appendPQExpBufferChar(cmd, '\''); appendStringLiteral(cmd, SYSTEMQUOTE, false); if (verbose) fprintf(stderr, _("%s: running \"%s\"\n"), progname, cmd->data); fflush(stdout); fflush(stderr); ret = system(cmd->data); destroyPQExpBuffer(cmd); return ret; } /* * Make a database connection with the given parameters. An * interactive password prompt is automatically issued if required. */ static PGconn * connectDatabase(const char *dbname, const char *pghost, const char *pgport, const char *pguser, bool require_password) { PGconn *conn; char *password = NULL; bool need_pass = false; const char *remoteversion_str; if (require_password) password = simple_prompt("Password: ", 100, false); /* * Start the connection. Loop until we have a password if requested * by backend. */ do { need_pass = false; conn = PQsetdbLogin(pghost, pgport, NULL, NULL, dbname, pguser, password); if (!conn) { fprintf(stderr, _("%s: could not connect to database \"%s\"\n"), progname, dbname); exit(1); } if (PQstatus(conn) == CONNECTION_BAD && strcmp(PQerrorMessage(conn), "fe_sendauth: no password supplied\n") == 0 && !feof(stdin)) { PQfinish(conn); need_pass = true; free(password); password = NULL; password = simple_prompt("Password: ", 100, false); } } while (need_pass); if (password) free(password); /* check to see that the backend connection was successfully made */ if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr, _("%s: could not connect to database \"%s\": %s\n"), progname, dbname, PQerrorMessage(conn)); exit(1); } remoteversion_str = PQparameterStatus(conn, "server_version"); if (!remoteversion_str) { fprintf(stderr, _("%s: could not get server version\n"), progname); exit(1); } server_version = parse_version(remoteversion_str); if (server_version < 0) { fprintf(stderr, _("%s: could not parse server version \"%s\"\n"), progname, remoteversion_str); exit(1); } return conn; } /* * Run a query, return the results, exit program on failure. */ static PGresult * executeQuery(PGconn *conn, const char *query) { PGresult *res; if (verbose) fprintf(stderr, _("%s: executing %s\n"), progname, query); res = PQexec(conn, query); if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) { fprintf(stderr, _("%s: query failed: %s"), progname, PQerrorMessage(conn)); fprintf(stderr, _("%s: query was: %s\n"), progname, query); PQfinish(conn); exit(1); } return res; } /* * dumpTimestamp */ static void dumpTimestamp(char *msg) { char buf[256]; time_t now = time(NULL); if (strftime(buf, 256, "%Y-%m-%d %H:%M:%S %Z", localtime(&now)) != 0) printf("-- %s %s\n\n", msg, buf); }