diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
commit | 293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch) | |
tree | fc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /src/bin/pg_dump/pg_dumpall.c | |
parent | Initial commit. (diff) | |
download | postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip |
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/pg_dump/pg_dumpall.c')
-rw-r--r-- | src/bin/pg_dump/pg_dumpall.c | 1927 |
1 files changed, 1927 insertions, 0 deletions
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c new file mode 100644 index 0000000..2cad796 --- /dev/null +++ b/src/bin/pg_dump/pg_dumpall.c @@ -0,0 +1,1927 @@ +/*------------------------------------------------------------------------- + * + * pg_dumpall.c + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * pg_dumpall forces all pg_dump output to be text, since it also outputs + * text into the same output stream. + * + * src/bin/pg_dump/pg_dumpall.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include <time.h> +#include <unistd.h> + +#include "catalog/pg_authid_d.h" +#include "common/connect.h" +#include "common/file_utils.h" +#include "common/hashfn.h" +#include "common/logging.h" +#include "common/string.h" +#include "dumputils.h" +#include "fe_utils/string_utils.h" +#include "getopt_long.h" +#include "pg_backup.h" + +/* version string we expect back from pg_dump */ +#define PGDUMP_VERSIONSTR "pg_dump (PostgreSQL) " PG_VERSION "\n" + +static uint32 hash_string_pointer(char *s); + +typedef struct +{ + uint32 status; + uint32 hashval; + char *rolename; +} RoleNameEntry; + +#define SH_PREFIX rolename +#define SH_ELEMENT_TYPE RoleNameEntry +#define SH_KEY_TYPE char * +#define SH_KEY rolename +#define SH_HASH_KEY(tb, key) hash_string_pointer(key) +#define SH_EQUAL(tb, a, b) (strcmp(a, b) == 0) +#define SH_STORE_HASH +#define SH_GET_HASH(tb, a) (a)->hashval +#define SH_SCOPE static inline +#define SH_RAW_ALLOCATOR pg_malloc0 +#define SH_DECLARE +#define SH_DEFINE +#include "lib/simplehash.h" + +static void help(void); + +static void dropRoles(PGconn *conn); +static void dumpRoles(PGconn *conn); +static void dumpRoleMembership(PGconn *conn); +static void dumpRoleGUCPrivs(PGconn *conn); +static void dropTablespaces(PGconn *conn); +static void dumpTablespaces(PGconn *conn); +static void dropDBs(PGconn *conn); +static void dumpUserConfig(PGconn *conn, const char *username); +static void dumpDatabases(PGconn *conn); +static void dumpTimestamp(const char *msg); +static int runPgDump(const char *dbname, const char *create_opts); +static void buildShSecLabels(PGconn *conn, + const char *catalog_name, Oid objectId, + const char *objtype, const char *objname, + PQExpBuffer buffer); +static PGconn *connectDatabase(const char *dbname, + const char *connection_string, const char *pghost, + const char *pgport, const char *pguser, + trivalue prompt_password, bool fail_on_error); +static char *constructConnStr(const char **keywords, const char **values); +static PGresult *executeQuery(PGconn *conn, const char *query); +static void executeCommand(PGconn *conn, const char *query); +static void expand_dbname_patterns(PGconn *conn, SimpleStringList *patterns, + SimpleStringList *names); + +static char pg_dump_bin[MAXPGPATH]; +static const char *progname; +static PQExpBuffer pgdumpopts; +static char *connstr = ""; +static bool output_clean = false; +static bool skip_acls = false; +static bool verbose = false; +static bool dosync = true; + +static int binary_upgrade = 0; +static int column_inserts = 0; +static int disable_dollar_quoting = 0; +static int disable_triggers = 0; +static int if_exists = 0; +static int inserts = 0; +static int no_table_access_method = 0; +static int no_tablespaces = 0; +static int use_setsessauth = 0; +static int no_comments = 0; +static int no_publications = 0; +static int no_security_labels = 0; +static int no_subscriptions = 0; +static int no_toast_compression = 0; +static int no_unlogged_table_data = 0; +static int no_role_passwords = 0; +static int server_version; +static int load_via_partition_root = 0; +static int on_conflict_do_nothing = 0; + +static char role_catalog[10]; +#define PG_AUTHID "pg_authid" +#define PG_ROLES "pg_roles " + +static FILE *OPF; +static char *filename = NULL; + +static SimpleStringList database_exclude_patterns = {NULL, NULL}; +static SimpleStringList database_exclude_names = {NULL, NULL}; + +#define exit_nicely(code) exit(code) + +int +main(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"data-only", no_argument, NULL, 'a'}, + {"clean", no_argument, NULL, 'c'}, + {"encoding", required_argument, NULL, 'E'}, + {"file", required_argument, NULL, 'f'}, + {"globals-only", no_argument, NULL, 'g'}, + {"host", required_argument, NULL, 'h'}, + {"dbname", required_argument, NULL, 'd'}, + {"database", required_argument, NULL, 'l'}, + {"no-owner", no_argument, NULL, 'O'}, + {"port", required_argument, NULL, 'p'}, + {"roles-only", no_argument, NULL, 'r'}, + {"schema-only", no_argument, NULL, 's'}, + {"superuser", required_argument, NULL, 'S'}, + {"tablespaces-only", no_argument, NULL, 't'}, + {"username", required_argument, NULL, 'U'}, + {"verbose", no_argument, NULL, 'v'}, + {"no-password", no_argument, NULL, 'w'}, + {"password", no_argument, NULL, 'W'}, + {"no-privileges", no_argument, NULL, 'x'}, + {"no-acl", no_argument, NULL, 'x'}, + + /* + * the following options don't have an equivalent short option letter + */ + {"attribute-inserts", no_argument, &column_inserts, 1}, + {"binary-upgrade", no_argument, &binary_upgrade, 1}, + {"column-inserts", no_argument, &column_inserts, 1}, + {"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1}, + {"disable-triggers", no_argument, &disable_triggers, 1}, + {"exclude-database", required_argument, NULL, 6}, + {"extra-float-digits", required_argument, NULL, 5}, + {"if-exists", no_argument, &if_exists, 1}, + {"inserts", no_argument, &inserts, 1}, + {"lock-wait-timeout", required_argument, NULL, 2}, + {"no-table-access-method", no_argument, &no_table_access_method, 1}, + {"no-tablespaces", no_argument, &no_tablespaces, 1}, + {"quote-all-identifiers", no_argument, "e_all_identifiers, 1}, + {"load-via-partition-root", no_argument, &load_via_partition_root, 1}, + {"role", required_argument, NULL, 3}, + {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, + {"no-comments", no_argument, &no_comments, 1}, + {"no-publications", no_argument, &no_publications, 1}, + {"no-role-passwords", no_argument, &no_role_passwords, 1}, + {"no-security-labels", no_argument, &no_security_labels, 1}, + {"no-subscriptions", no_argument, &no_subscriptions, 1}, + {"no-sync", no_argument, NULL, 4}, + {"no-toast-compression", no_argument, &no_toast_compression, 1}, + {"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1}, + {"on-conflict-do-nothing", no_argument, &on_conflict_do_nothing, 1}, + {"rows-per-insert", required_argument, NULL, 7}, + + {NULL, 0, NULL, 0} + }; + + char *pghost = NULL; + char *pgport = NULL; + char *pguser = NULL; + char *pgdb = NULL; + char *use_role = NULL; + const char *dumpencoding = NULL; + trivalue prompt_password = TRI_DEFAULT; + bool data_only = false; + bool globals_only = false; + bool roles_only = false; + bool tablespaces_only = false; + PGconn *conn; + int encoding; + const char *std_strings; + int c, + ret; + int optindex; + + pg_logging_init(argv[0]); + pg_logging_set_level(PG_LOG_WARNING); + set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_dump")); + progname = get_progname(argv[0]); + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + help(); + exit_nicely(0); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + puts("pg_dumpall (PostgreSQL) " PG_VERSION); + exit_nicely(0); + } + } + + if ((ret = find_other_exec(argv[0], "pg_dump", PGDUMP_VERSIONSTR, + pg_dump_bin)) < 0) + { + char full_path[MAXPGPATH]; + + if (find_my_exec(argv[0], full_path) < 0) + strlcpy(full_path, progname, sizeof(full_path)); + + if (ret == -1) + pg_fatal("program \"%s\" is needed by %s but was not found in the same directory as \"%s\"", + "pg_dump", progname, full_path); + else + pg_fatal("program \"%s\" was found by \"%s\" but was not the same version as %s", + "pg_dump", full_path, progname); + } + + pgdumpopts = createPQExpBuffer(); + + while ((c = getopt_long(argc, argv, "acd:E:f:gh:l:Op:rsS:tU:vwWx", long_options, &optindex)) != -1) + { + switch (c) + { + case 'a': + data_only = true; + appendPQExpBufferStr(pgdumpopts, " -a"); + break; + + case 'c': + output_clean = true; + break; + + case 'd': + connstr = pg_strdup(optarg); + break; + + case 'E': + dumpencoding = pg_strdup(optarg); + appendPQExpBufferStr(pgdumpopts, " -E "); + appendShellString(pgdumpopts, optarg); + break; + + case 'f': + filename = pg_strdup(optarg); + appendPQExpBufferStr(pgdumpopts, " -f "); + appendShellString(pgdumpopts, filename); + break; + + case 'g': + globals_only = true; + break; + + case 'h': + pghost = pg_strdup(optarg); + break; + + case 'l': + pgdb = pg_strdup(optarg); + break; + + case 'O': + appendPQExpBufferStr(pgdumpopts, " -O"); + break; + + case 'p': + pgport = pg_strdup(optarg); + break; + + case 'r': + roles_only = true; + break; + + case 's': + appendPQExpBufferStr(pgdumpopts, " -s"); + break; + + case 'S': + appendPQExpBufferStr(pgdumpopts, " -S "); + appendShellString(pgdumpopts, optarg); + break; + + case 't': + tablespaces_only = true; + break; + + case 'U': + pguser = pg_strdup(optarg); + break; + + case 'v': + verbose = true; + pg_logging_increase_verbosity(); + appendPQExpBufferStr(pgdumpopts, " -v"); + break; + + case 'w': + prompt_password = TRI_NO; + appendPQExpBufferStr(pgdumpopts, " -w"); + break; + + case 'W': + prompt_password = TRI_YES; + appendPQExpBufferStr(pgdumpopts, " -W"); + break; + + case 'x': + skip_acls = true; + appendPQExpBufferStr(pgdumpopts, " -x"); + break; + + case 0: + break; + + case 2: + appendPQExpBufferStr(pgdumpopts, " --lock-wait-timeout "); + appendShellString(pgdumpopts, optarg); + break; + + case 3: + use_role = pg_strdup(optarg); + appendPQExpBufferStr(pgdumpopts, " --role "); + appendShellString(pgdumpopts, use_role); + break; + + case 4: + dosync = false; + appendPQExpBufferStr(pgdumpopts, " --no-sync"); + break; + + case 5: + appendPQExpBufferStr(pgdumpopts, " --extra-float-digits "); + appendShellString(pgdumpopts, optarg); + break; + + case 6: + simple_string_list_append(&database_exclude_patterns, optarg); + break; + + case 7: + appendPQExpBufferStr(pgdumpopts, " --rows-per-insert "); + appendShellString(pgdumpopts, optarg); + break; + + default: + /* getopt_long already emitted a complaint */ + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit_nicely(1); + } + } + + /* Complain if any arguments remain */ + if (optind < argc) + { + pg_log_error("too many command-line arguments (first is \"%s\")", + argv[optind]); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit_nicely(1); + } + + if (database_exclude_patterns.head != NULL && + (globals_only || roles_only || tablespaces_only)) + { + pg_log_error("option --exclude-database cannot be used together with -g/--globals-only, -r/--roles-only, or -t/--tablespaces-only"); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit_nicely(1); + } + + /* Make sure the user hasn't specified a mix of globals-only options */ + if (globals_only && roles_only) + { + pg_log_error("options -g/--globals-only and -r/--roles-only cannot be used together"); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit_nicely(1); + } + + if (globals_only && tablespaces_only) + { + pg_log_error("options -g/--globals-only and -t/--tablespaces-only cannot be used together"); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit_nicely(1); + } + + if (if_exists && !output_clean) + pg_fatal("option --if-exists requires option -c/--clean"); + + if (roles_only && tablespaces_only) + { + pg_log_error("options -r/--roles-only and -t/--tablespaces-only cannot be used together"); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit_nicely(1); + } + + /* + * If password values are not required in the dump, switch to using + * pg_roles which is equally useful, just more likely to have unrestricted + * access than pg_authid. + */ + if (no_role_passwords) + sprintf(role_catalog, "%s", PG_ROLES); + else + sprintf(role_catalog, "%s", PG_AUTHID); + + /* Add long options to the pg_dump argument list */ + if (binary_upgrade) + appendPQExpBufferStr(pgdumpopts, " --binary-upgrade"); + if (column_inserts) + appendPQExpBufferStr(pgdumpopts, " --column-inserts"); + if (disable_dollar_quoting) + appendPQExpBufferStr(pgdumpopts, " --disable-dollar-quoting"); + if (disable_triggers) + appendPQExpBufferStr(pgdumpopts, " --disable-triggers"); + if (inserts) + appendPQExpBufferStr(pgdumpopts, " --inserts"); + if (no_table_access_method) + appendPQExpBufferStr(pgdumpopts, " --no-table-access-method"); + if (no_tablespaces) + appendPQExpBufferStr(pgdumpopts, " --no-tablespaces"); + if (quote_all_identifiers) + appendPQExpBufferStr(pgdumpopts, " --quote-all-identifiers"); + if (load_via_partition_root) + appendPQExpBufferStr(pgdumpopts, " --load-via-partition-root"); + if (use_setsessauth) + appendPQExpBufferStr(pgdumpopts, " --use-set-session-authorization"); + if (no_comments) + appendPQExpBufferStr(pgdumpopts, " --no-comments"); + if (no_publications) + appendPQExpBufferStr(pgdumpopts, " --no-publications"); + if (no_security_labels) + appendPQExpBufferStr(pgdumpopts, " --no-security-labels"); + if (no_subscriptions) + appendPQExpBufferStr(pgdumpopts, " --no-subscriptions"); + if (no_toast_compression) + appendPQExpBufferStr(pgdumpopts, " --no-toast-compression"); + if (no_unlogged_table_data) + appendPQExpBufferStr(pgdumpopts, " --no-unlogged-table-data"); + if (on_conflict_do_nothing) + appendPQExpBufferStr(pgdumpopts, " --on-conflict-do-nothing"); + + /* + * If there was a database specified on the command line, use that, + * otherwise try to connect to database "postgres", and failing that + * "template1". + */ + if (pgdb) + { + conn = connectDatabase(pgdb, connstr, pghost, pgport, pguser, + prompt_password, false); + + if (!conn) + pg_fatal("could not connect to database \"%s\"", pgdb); + } + else + { + conn = connectDatabase("postgres", connstr, pghost, pgport, pguser, + prompt_password, false); + if (!conn) + conn = connectDatabase("template1", connstr, pghost, pgport, pguser, + prompt_password, true); + + if (!conn) + { + pg_log_error("could not connect to databases \"postgres\" or \"template1\"\n" + "Please specify an alternative database."); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit_nicely(1); + } + } + + /* + * Get a list of database names that match the exclude patterns + */ + expand_dbname_patterns(conn, &database_exclude_patterns, + &database_exclude_names); + + /* + * Open the output file if required, otherwise use stdout + */ + if (filename) + { + OPF = fopen(filename, PG_BINARY_W); + if (!OPF) + pg_fatal("could not open output file \"%s\": %m", + filename); + } + else + OPF = stdout; + + /* + * Set the client encoding if requested. + */ + if (dumpencoding) + { + if (PQsetClientEncoding(conn, dumpencoding) < 0) + pg_fatal("invalid client encoding \"%s\" specified", + dumpencoding); + } + + /* + * Get the active encoding and the standard_conforming_strings setting, so + * we know how to escape strings. + */ + encoding = PQclientEncoding(conn); + std_strings = PQparameterStatus(conn, "standard_conforming_strings"); + if (!std_strings) + std_strings = "off"; + + /* Set the role if requested */ + if (use_role) + { + PQExpBuffer query = createPQExpBuffer(); + + appendPQExpBuffer(query, "SET ROLE %s", fmtId(use_role)); + executeCommand(conn, query->data); + destroyPQExpBuffer(query); + } + + /* Force quoting of all identifiers if requested. */ + if (quote_all_identifiers) + executeCommand(conn, "SET quote_all_identifiers = true"); + + fprintf(OPF, "--\n-- PostgreSQL database cluster dump\n--\n\n"); + if (verbose) + dumpTimestamp("Started on"); + + /* + * We used to emit \connect postgres here, but that served no purpose + * other than to break things for installations without a postgres + * database. Everything we're restoring here is a global, so whichever + * database we're connected to at the moment is fine. + */ + + /* Restore will need to write to the target cluster */ + fprintf(OPF, "SET default_transaction_read_only = off;\n\n"); + + /* Replicate encoding and std_strings in output */ + fprintf(OPF, "SET client_encoding = '%s';\n", + pg_encoding_to_char(encoding)); + fprintf(OPF, "SET standard_conforming_strings = %s;\n", std_strings); + if (strcmp(std_strings, "off") == 0) + fprintf(OPF, "SET escape_string_warning = off;\n"); + fprintf(OPF, "\n"); + + if (!data_only) + { + /* + * If asked to --clean, do that first. We can avoid detailed + * dependency analysis because databases never depend on each other, + * and tablespaces never depend on each other. Roles could have + * grants to each other, but DROP ROLE will clean those up silently. + */ + if (output_clean) + { + if (!globals_only && !roles_only && !tablespaces_only) + dropDBs(conn); + + if (!roles_only && !no_tablespaces) + dropTablespaces(conn); + + if (!tablespaces_only) + dropRoles(conn); + } + + /* + * Now create objects as requested. Be careful that option logic here + * is the same as for drops above. + */ + if (!tablespaces_only) + { + /* Dump roles (users) */ + dumpRoles(conn); + + /* Dump role memberships */ + dumpRoleMembership(conn); + + /* Dump role GUC privileges */ + if (server_version >= 150000 && !skip_acls) + dumpRoleGUCPrivs(conn); + } + + /* Dump tablespaces */ + if (!roles_only && !no_tablespaces) + dumpTablespaces(conn); + } + + if (!globals_only && !roles_only && !tablespaces_only) + dumpDatabases(conn); + + PQfinish(conn); + + if (verbose) + dumpTimestamp("Completed on"); + fprintf(OPF, "--\n-- PostgreSQL database cluster dump complete\n--\n\n"); + + if (filename) + { + fclose(OPF); + + /* sync the resulting file, errors are not fatal */ + if (dosync) + (void) fsync_fname(filename, false); + } + + exit_nicely(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(_("\nGeneral options:\n")); + printf(_(" -f, --file=FILENAME output file name\n")); + printf(_(" -v, --verbose verbose mode\n")); + printf(_(" -V, --version output version information, then exit\n")); + printf(_(" --lock-wait-timeout=TIMEOUT fail after waiting TIMEOUT for a table lock\n")); + printf(_(" -?, --help show this help, then exit\n")); + printf(_("\nOptions controlling the output content:\n")); + printf(_(" -a, --data-only dump only the data, not the schema\n")); + printf(_(" -c, --clean clean (drop) databases before recreating\n")); + printf(_(" -E, --encoding=ENCODING dump the data in encoding ENCODING\n")); + printf(_(" -g, --globals-only dump only global objects, no databases\n")); + printf(_(" -O, --no-owner skip restoration of object ownership\n")); + printf(_(" -r, --roles-only dump only roles, no databases or tablespaces\n")); + printf(_(" -s, --schema-only dump only the schema, no data\n")); + printf(_(" -S, --superuser=NAME superuser user name to use in the dump\n")); + printf(_(" -t, --tablespaces-only dump only tablespaces, no databases or roles\n")); + printf(_(" -x, --no-privileges do not dump privileges (grant/revoke)\n")); + printf(_(" --binary-upgrade for use by upgrade utilities only\n")); + printf(_(" --column-inserts dump data as INSERT commands with column names\n")); + printf(_(" --disable-dollar-quoting disable dollar quoting, use SQL standard quoting\n")); + printf(_(" --disable-triggers disable triggers during data-only restore\n")); + printf(_(" --exclude-database=PATTERN exclude databases whose name matches PATTERN\n")); + printf(_(" --extra-float-digits=NUM override default setting for extra_float_digits\n")); + printf(_(" --if-exists use IF EXISTS when dropping objects\n")); + printf(_(" --inserts dump data as INSERT commands, rather than COPY\n")); + printf(_(" --load-via-partition-root load partitions via the root table\n")); + printf(_(" --no-comments do not dump comments\n")); + printf(_(" --no-publications do not dump publications\n")); + printf(_(" --no-role-passwords do not dump passwords for roles\n")); + printf(_(" --no-security-labels do not dump security label assignments\n")); + printf(_(" --no-subscriptions do not dump subscriptions\n")); + printf(_(" --no-sync do not wait for changes to be written safely to disk\n")); + printf(_(" --no-table-access-method do not dump table access methods\n")); + printf(_(" --no-tablespaces do not dump tablespace assignments\n")); + printf(_(" --no-toast-compression do not dump TOAST compression methods\n")); + printf(_(" --no-unlogged-table-data do not dump unlogged table data\n")); + printf(_(" --on-conflict-do-nothing add ON CONFLICT DO NOTHING to INSERT commands\n")); + printf(_(" --quote-all-identifiers quote all identifiers, even if not key words\n")); + printf(_(" --rows-per-insert=NROWS number of rows per INSERT; implies --inserts\n")); + printf(_(" --use-set-session-authorization\n" + " use SET SESSION AUTHORIZATION commands instead of\n" + " ALTER OWNER commands to set ownership\n")); + + printf(_("\nConnection options:\n")); + printf(_(" -d, --dbname=CONNSTR connect using connection string\n")); + printf(_(" -h, --host=HOSTNAME database server host or socket directory\n")); + printf(_(" -l, --database=DBNAME alternative default database\n")); + printf(_(" -p, --port=PORT database server port number\n")); + printf(_(" -U, --username=NAME connect as specified database user\n")); + printf(_(" -w, --no-password never prompt for password\n")); + printf(_(" -W, --password force password prompt (should happen automatically)\n")); + printf(_(" --role=ROLENAME do SET ROLE before dump\n")); + + printf(_("\nIf -f/--file is not used, then the SQL script will be written to the standard\n" + "output.\n\n")); + printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT); + printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL); +} + + +/* + * Drop roles + */ +static void +dropRoles(PGconn *conn) +{ + PQExpBuffer buf = createPQExpBuffer(); + PGresult *res; + int i_rolname; + int i; + + if (server_version >= 90600) + printfPQExpBuffer(buf, + "SELECT rolname " + "FROM %s " + "WHERE rolname !~ '^pg_' " + "ORDER BY 1", role_catalog); + else + printfPQExpBuffer(buf, + "SELECT rolname " + "FROM %s " + "ORDER BY 1", role_catalog); + + res = executeQuery(conn, buf->data); + + i_rolname = PQfnumber(res, "rolname"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Drop roles\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + const char *rolename; + + rolename = PQgetvalue(res, i, i_rolname); + + fprintf(OPF, "DROP ROLE %s%s;\n", + if_exists ? "IF EXISTS " : "", + fmtId(rolename)); + } + + PQclear(res); + destroyPQExpBuffer(buf); + + fprintf(OPF, "\n\n"); +} + +/* + * Dump roles + */ +static void +dumpRoles(PGconn *conn) +{ + PQExpBuffer buf = createPQExpBuffer(); + PGresult *res; + int i_oid, + i_rolname, + i_rolsuper, + i_rolinherit, + i_rolcreaterole, + i_rolcreatedb, + i_rolcanlogin, + i_rolconnlimit, + i_rolpassword, + i_rolvaliduntil, + i_rolreplication, + i_rolbypassrls, + i_rolcomment, + i_is_current_user; + int i; + + /* note: rolconfig is dumped later */ + if (server_version >= 90600) + printfPQExpBuffer(buf, + "SELECT oid, rolname, rolsuper, rolinherit, " + "rolcreaterole, rolcreatedb, " + "rolcanlogin, rolconnlimit, rolpassword, " + "rolvaliduntil, rolreplication, rolbypassrls, " + "pg_catalog.shobj_description(oid, '%s') as rolcomment, " + "rolname = current_user AS is_current_user " + "FROM %s " + "WHERE rolname !~ '^pg_' " + "ORDER BY 2", role_catalog, role_catalog); + else if (server_version >= 90500) + printfPQExpBuffer(buf, + "SELECT oid, rolname, rolsuper, rolinherit, " + "rolcreaterole, rolcreatedb, " + "rolcanlogin, rolconnlimit, rolpassword, " + "rolvaliduntil, rolreplication, rolbypassrls, " + "pg_catalog.shobj_description(oid, '%s') as rolcomment, " + "rolname = current_user AS is_current_user " + "FROM %s " + "ORDER BY 2", role_catalog, role_catalog); + else + printfPQExpBuffer(buf, + "SELECT oid, rolname, rolsuper, rolinherit, " + "rolcreaterole, rolcreatedb, " + "rolcanlogin, rolconnlimit, rolpassword, " + "rolvaliduntil, rolreplication, " + "false as rolbypassrls, " + "pg_catalog.shobj_description(oid, '%s') as rolcomment, " + "rolname = current_user AS is_current_user " + "FROM %s " + "ORDER BY 2", role_catalog, role_catalog); + + res = executeQuery(conn, buf->data); + + i_oid = PQfnumber(res, "oid"); + i_rolname = PQfnumber(res, "rolname"); + i_rolsuper = PQfnumber(res, "rolsuper"); + i_rolinherit = PQfnumber(res, "rolinherit"); + i_rolcreaterole = PQfnumber(res, "rolcreaterole"); + i_rolcreatedb = PQfnumber(res, "rolcreatedb"); + i_rolcanlogin = PQfnumber(res, "rolcanlogin"); + i_rolconnlimit = PQfnumber(res, "rolconnlimit"); + i_rolpassword = PQfnumber(res, "rolpassword"); + i_rolvaliduntil = PQfnumber(res, "rolvaliduntil"); + i_rolreplication = PQfnumber(res, "rolreplication"); + i_rolbypassrls = PQfnumber(res, "rolbypassrls"); + i_rolcomment = PQfnumber(res, "rolcomment"); + i_is_current_user = PQfnumber(res, "is_current_user"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Roles\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + const char *rolename; + Oid auth_oid; + + auth_oid = atooid(PQgetvalue(res, i, i_oid)); + rolename = PQgetvalue(res, i, i_rolname); + + if (strncmp(rolename, "pg_", 3) == 0) + { + pg_log_warning("role name starting with \"pg_\" skipped (%s)", + rolename); + continue; + } + + resetPQExpBuffer(buf); + + if (binary_upgrade) + { + appendPQExpBufferStr(buf, "\n-- For binary upgrade, must preserve pg_authid.oid\n"); + appendPQExpBuffer(buf, + "SELECT pg_catalog.binary_upgrade_set_next_pg_authid_oid('%u'::pg_catalog.oid);\n\n", + auth_oid); + } + + /* + * We dump CREATE ROLE followed by ALTER ROLE to ensure that the role + * will acquire the right properties even if it already exists (ie, it + * won't hurt for the CREATE to fail). This is particularly important + * for the role we are connected as, since even with --clean we will + * have failed to drop it. binary_upgrade cannot generate any errors, + * so we assume the current role is already created. + */ + if (!binary_upgrade || + strcmp(PQgetvalue(res, i, i_is_current_user), "f") == 0) + appendPQExpBuffer(buf, "CREATE ROLE %s;\n", fmtId(rolename)); + appendPQExpBuffer(buf, "ALTER ROLE %s WITH", fmtId(rolename)); + + if (strcmp(PQgetvalue(res, i, i_rolsuper), "t") == 0) + appendPQExpBufferStr(buf, " SUPERUSER"); + else + appendPQExpBufferStr(buf, " NOSUPERUSER"); + + if (strcmp(PQgetvalue(res, i, i_rolinherit), "t") == 0) + appendPQExpBufferStr(buf, " INHERIT"); + else + appendPQExpBufferStr(buf, " NOINHERIT"); + + if (strcmp(PQgetvalue(res, i, i_rolcreaterole), "t") == 0) + appendPQExpBufferStr(buf, " CREATEROLE"); + else + appendPQExpBufferStr(buf, " NOCREATEROLE"); + + if (strcmp(PQgetvalue(res, i, i_rolcreatedb), "t") == 0) + appendPQExpBufferStr(buf, " CREATEDB"); + else + appendPQExpBufferStr(buf, " NOCREATEDB"); + + if (strcmp(PQgetvalue(res, i, i_rolcanlogin), "t") == 0) + appendPQExpBufferStr(buf, " LOGIN"); + else + appendPQExpBufferStr(buf, " NOLOGIN"); + + if (strcmp(PQgetvalue(res, i, i_rolreplication), "t") == 0) + appendPQExpBufferStr(buf, " REPLICATION"); + else + appendPQExpBufferStr(buf, " NOREPLICATION"); + + if (strcmp(PQgetvalue(res, i, i_rolbypassrls), "t") == 0) + appendPQExpBufferStr(buf, " BYPASSRLS"); + else + appendPQExpBufferStr(buf, " NOBYPASSRLS"); + + if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0) + appendPQExpBuffer(buf, " CONNECTION LIMIT %s", + PQgetvalue(res, i, i_rolconnlimit)); + + + if (!PQgetisnull(res, i, i_rolpassword) && !no_role_passwords) + { + appendPQExpBufferStr(buf, " PASSWORD "); + appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolpassword), conn); + } + + if (!PQgetisnull(res, i, i_rolvaliduntil)) + appendPQExpBuffer(buf, " VALID UNTIL '%s'", + PQgetvalue(res, i, i_rolvaliduntil)); + + appendPQExpBufferStr(buf, ";\n"); + + if (!no_comments && !PQgetisnull(res, i, i_rolcomment)) + { + appendPQExpBuffer(buf, "COMMENT ON ROLE %s IS ", fmtId(rolename)); + appendStringLiteralConn(buf, PQgetvalue(res, i, i_rolcomment), conn); + appendPQExpBufferStr(buf, ";\n"); + } + + if (!no_security_labels) + buildShSecLabels(conn, "pg_authid", auth_oid, + "ROLE", rolename, + buf); + + fprintf(OPF, "%s", buf->data); + } + + /* + * Dump configuration settings for roles after all roles have been dumped. + * We do it this way because config settings for roles could mention the + * names of other roles. + */ + if (PQntuples(res) > 0) + fprintf(OPF, "\n--\n-- User Configurations\n--\n"); + + for (i = 0; i < PQntuples(res); i++) + dumpUserConfig(conn, PQgetvalue(res, i, i_rolname)); + + PQclear(res); + + fprintf(OPF, "\n\n"); + + destroyPQExpBuffer(buf); +} + + +/* + * Dump role memberships. + * + * Note: we expect dumpRoles already created all the roles, but there is + * no membership yet. + */ +static void +dumpRoleMembership(PGconn *conn) +{ + PQExpBuffer buf = createPQExpBuffer(); + PQExpBuffer optbuf = createPQExpBuffer(); + PGresult *res; + int start = 0, + end, + total; + bool dump_grantors; + bool dump_grant_options; + int i_inherit_option; + int i_set_option; + + /* + * Previous versions of PostgreSQL didn't used to track the grantor very + * carefully in the backend, and the grantor could be any user even if + * they didn't have ADMIN OPTION on the role, or a user that no longer + * existed. To avoid dump and restore failures, don't dump the grantor + * when talking to an old server version. + */ + dump_grantors = (PQserverVersion(conn) >= 160000); + + /* + * Previous versions of PostgreSQL also did not have grant-level options. + */ + dump_grant_options = (server_version >= 160000); + + /* Generate and execute query. */ + printfPQExpBuffer(buf, "SELECT ur.rolname AS role, " + "um.rolname AS member, " + "ug.oid AS grantorid, " + "ug.rolname AS grantor, " + "a.admin_option"); + if (dump_grant_options) + appendPQExpBufferStr(buf, ", a.inherit_option, a.set_option"); + appendPQExpBuffer(buf, " FROM pg_auth_members a " + "LEFT JOIN %s ur on ur.oid = a.roleid " + "LEFT JOIN %s um on um.oid = a.member " + "LEFT JOIN %s ug on ug.oid = a.grantor " + "WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')" + "ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog); + res = executeQuery(conn, buf->data); + i_inherit_option = PQfnumber(res, "inherit_option"); + i_set_option = PQfnumber(res, "set_option"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Role memberships\n--\n\n"); + + /* + * We can't dump these GRANT commands in arbitrary order, because a role + * that is named as a grantor must already have ADMIN OPTION on the role + * for which it is granting permissions, except for the bootstrap + * superuser, who can always be named as the grantor. + * + * We handle this by considering these grants role by role. For each role, + * we initially consider the only allowable grantor to be the bootstrap + * superuser. Every time we grant ADMIN OPTION on the role to some user, + * that user also becomes an allowable grantor. We make repeated passes + * over the grants for the role, each time dumping those whose grantors + * are allowable and which we haven't done yet. Eventually this should let + * us dump all the grants. + */ + total = PQntuples(res); + while (start < total) + { + char *role = PQgetvalue(res, start, 0); + int i; + bool *done; + int remaining; + int prev_remaining = 0; + rolename_hash *ht; + + /* All memberships for a single role should be adjacent. */ + for (end = start; end < total; ++end) + { + char *otherrole; + + otherrole = PQgetvalue(res, end, 0); + if (strcmp(role, otherrole) != 0) + break; + } + + role = PQgetvalue(res, start, 0); + remaining = end - start; + done = pg_malloc0(remaining * sizeof(bool)); + ht = rolename_create(remaining, NULL); + + /* + * Make repeated passes over the grants for this role until all have + * been dumped. + */ + while (remaining > 0) + { + /* + * We should make progress on every iteration, because a notional + * graph whose vertices are grants and whose edges point from + * grantors to members should be connected and acyclic. If we fail + * to make progress, either we or the server have messed up. + */ + if (remaining == prev_remaining) + { + pg_log_error("could not find a legal dump ordering for memberships in role \"%s\"", + role); + PQfinish(conn); + exit_nicely(1); + } + prev_remaining = remaining; + + /* Make one pass over the grants for this role. */ + for (i = start; i < end; ++i) + { + char *member; + char *admin_option; + char *grantorid; + char *grantor; + char *set_option = "true"; + bool found; + + /* If we already did this grant, don't do it again. */ + if (done[i - start]) + continue; + + member = PQgetvalue(res, i, 1); + grantorid = PQgetvalue(res, i, 2); + grantor = PQgetvalue(res, i, 3); + admin_option = PQgetvalue(res, i, 4); + if (dump_grant_options) + set_option = PQgetvalue(res, i, i_set_option); + + /* + * If we're not dumping grantors or if the grantor is the + * bootstrap superuser, it's fine to dump this now. Otherwise, + * it's got to be someone who has already been granted ADMIN + * OPTION. + */ + if (dump_grantors && + atooid(grantorid) != BOOTSTRAP_SUPERUSERID && + rolename_lookup(ht, grantor) == NULL) + continue; + + /* Remember that we did this so that we don't do it again. */ + done[i - start] = true; + --remaining; + + /* + * If ADMIN OPTION is being granted, remember that grants + * listing this member as the grantor can now be dumped. + */ + if (*admin_option == 't') + rolename_insert(ht, member, &found); + + /* Generate the actual GRANT statement. */ + resetPQExpBuffer(optbuf); + fprintf(OPF, "GRANT %s", fmtId(role)); + fprintf(OPF, " TO %s", fmtId(member)); + if (*admin_option == 't') + appendPQExpBufferStr(optbuf, "ADMIN OPTION"); + if (dump_grant_options) + { + char *inherit_option; + + if (optbuf->data[0] != '\0') + appendPQExpBufferStr(optbuf, ", "); + inherit_option = PQgetvalue(res, i, i_inherit_option); + appendPQExpBuffer(optbuf, "INHERIT %s", + *inherit_option == 't' ? + "TRUE" : "FALSE"); + } + if (*set_option != 't') + { + if (optbuf->data[0] != '\0') + appendPQExpBufferStr(optbuf, ", "); + appendPQExpBuffer(optbuf, "SET FALSE"); + } + if (optbuf->data[0] != '\0') + fprintf(OPF, " WITH %s", optbuf->data); + if (dump_grantors) + fprintf(OPF, " GRANTED BY %s", fmtId(grantor)); + fprintf(OPF, ";\n"); + } + } + + rolename_destroy(ht); + pg_free(done); + start = end; + } + + PQclear(res); + destroyPQExpBuffer(buf); + + fprintf(OPF, "\n\n"); +} + + +/* + * Dump role configuration parameter privileges. This code is used for 15.0 + * and later servers. + * + * Note: we expect dumpRoles already created all the roles, but there are + * no per-role configuration parameter privileges yet. + */ +static void +dumpRoleGUCPrivs(PGconn *conn) +{ + PGresult *res; + int i; + + /* + * Get all parameters that have non-default acls defined. + */ + res = executeQuery(conn, "SELECT parname, " + "pg_catalog.pg_get_userbyid(" CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS parowner, " + "paracl, " + "pg_catalog.acldefault('p', " CppAsString2(BOOTSTRAP_SUPERUSERID) ") AS acldefault " + "FROM pg_catalog.pg_parameter_acl " + "ORDER BY 1"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Role privileges on configuration parameters\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + PQExpBuffer buf = createPQExpBuffer(); + char *parname = PQgetvalue(res, i, 0); + char *parowner = PQgetvalue(res, i, 1); + char *paracl = PQgetvalue(res, i, 2); + char *acldefault = PQgetvalue(res, i, 3); + char *fparname; + + /* needed for buildACLCommands() */ + fparname = pg_strdup(fmtId(parname)); + + if (!buildACLCommands(fparname, NULL, NULL, "PARAMETER", + paracl, acldefault, + parowner, "", server_version, buf)) + { + pg_log_error("could not parse ACL list (%s) for parameter \"%s\"", + paracl, parname); + PQfinish(conn); + exit_nicely(1); + } + + fprintf(OPF, "%s", buf->data); + + free(fparname); + destroyPQExpBuffer(buf); + } + + PQclear(res); + fprintf(OPF, "\n\n"); +} + + +/* + * Drop tablespaces. + */ +static void +dropTablespaces(PGconn *conn) +{ + PGresult *res; + int i; + + /* + * Get all tablespaces except built-in ones (which we assume are named + * pg_xxx) + */ + res = executeQuery(conn, "SELECT spcname " + "FROM pg_catalog.pg_tablespace " + "WHERE spcname !~ '^pg_' " + "ORDER BY 1"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Drop tablespaces\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + char *spcname = PQgetvalue(res, i, 0); + + fprintf(OPF, "DROP TABLESPACE %s%s;\n", + if_exists ? "IF EXISTS " : "", + fmtId(spcname)); + } + + PQclear(res); + + fprintf(OPF, "\n\n"); +} + +/* + * Dump tablespaces. + */ +static void +dumpTablespaces(PGconn *conn) +{ + PGresult *res; + int i; + + /* + * Get all tablespaces except built-in ones (which we assume are named + * pg_xxx) + */ + res = executeQuery(conn, "SELECT oid, spcname, " + "pg_catalog.pg_get_userbyid(spcowner) AS spcowner, " + "pg_catalog.pg_tablespace_location(oid), " + "spcacl, acldefault('t', spcowner) AS acldefault, " + "array_to_string(spcoptions, ', ')," + "pg_catalog.shobj_description(oid, 'pg_tablespace') " + "FROM pg_catalog.pg_tablespace " + "WHERE spcname !~ '^pg_' " + "ORDER BY 1"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Tablespaces\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + PQExpBuffer buf = createPQExpBuffer(); + Oid spcoid = atooid(PQgetvalue(res, i, 0)); + char *spcname = PQgetvalue(res, i, 1); + char *spcowner = PQgetvalue(res, i, 2); + char *spclocation = PQgetvalue(res, i, 3); + char *spcacl = PQgetvalue(res, i, 4); + char *acldefault = PQgetvalue(res, i, 5); + char *spcoptions = PQgetvalue(res, i, 6); + char *spccomment = PQgetvalue(res, i, 7); + char *fspcname; + + /* needed for buildACLCommands() */ + fspcname = pg_strdup(fmtId(spcname)); + + if (binary_upgrade) + { + appendPQExpBufferStr(buf, "\n-- For binary upgrade, must preserve pg_tablespace oid\n"); + appendPQExpBuffer(buf, "SELECT pg_catalog.binary_upgrade_set_next_pg_tablespace_oid('%u'::pg_catalog.oid);\n", spcoid); + } + + appendPQExpBuffer(buf, "CREATE TABLESPACE %s", fspcname); + appendPQExpBuffer(buf, " OWNER %s", fmtId(spcowner)); + + appendPQExpBufferStr(buf, " LOCATION "); + appendStringLiteralConn(buf, spclocation, conn); + appendPQExpBufferStr(buf, ";\n"); + + if (spcoptions && spcoptions[0] != '\0') + appendPQExpBuffer(buf, "ALTER TABLESPACE %s SET (%s);\n", + fspcname, spcoptions); + + /* tablespaces can't have initprivs */ + + if (!skip_acls && + !buildACLCommands(fspcname, NULL, NULL, "TABLESPACE", + spcacl, acldefault, + spcowner, "", server_version, buf)) + { + pg_log_error("could not parse ACL list (%s) for tablespace \"%s\"", + spcacl, spcname); + PQfinish(conn); + exit_nicely(1); + } + + if (!no_comments && spccomment && spccomment[0] != '\0') + { + appendPQExpBuffer(buf, "COMMENT ON TABLESPACE %s IS ", fspcname); + appendStringLiteralConn(buf, spccomment, conn); + appendPQExpBufferStr(buf, ";\n"); + } + + if (!no_security_labels) + buildShSecLabels(conn, "pg_tablespace", spcoid, + "TABLESPACE", spcname, + buf); + + fprintf(OPF, "%s", buf->data); + + free(fspcname); + destroyPQExpBuffer(buf); + } + + PQclear(res); + fprintf(OPF, "\n\n"); +} + + +/* + * Dump commands to drop each database. + */ +static void +dropDBs(PGconn *conn) +{ + PGresult *res; + int i; + + /* + * Skip databases marked not datallowconn, since we'd be unable to connect + * to them anyway. This must agree with dumpDatabases(). + */ + res = executeQuery(conn, + "SELECT datname " + "FROM pg_database d " + "WHERE datallowconn AND datconnlimit != -2 " + "ORDER BY datname"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Drop databases (except postgres and template1)\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + char *dbname = PQgetvalue(res, i, 0); + + /* + * Skip "postgres" and "template1"; dumpDatabases() will deal with + * them specially. Also, be sure to skip "template0", even if for + * some reason it's not marked !datallowconn. + */ + if (strcmp(dbname, "template1") != 0 && + strcmp(dbname, "template0") != 0 && + strcmp(dbname, "postgres") != 0) + { + fprintf(OPF, "DROP DATABASE %s%s;\n", + if_exists ? "IF EXISTS " : "", + fmtId(dbname)); + } + } + + PQclear(res); + + fprintf(OPF, "\n\n"); +} + + +/* + * Dump user-specific configuration + */ +static void +dumpUserConfig(PGconn *conn, const char *username) +{ + PQExpBuffer buf = createPQExpBuffer(); + PGresult *res; + + printfPQExpBuffer(buf, "SELECT unnest(setconfig) FROM pg_db_role_setting " + "WHERE setdatabase = 0 AND setrole = " + "(SELECT oid FROM %s WHERE rolname = ", + role_catalog); + appendStringLiteralConn(buf, username, conn); + appendPQExpBufferChar(buf, ')'); + + res = executeQuery(conn, buf->data); + + if (PQntuples(res) > 0) + fprintf(OPF, "\n--\n-- User Config \"%s\"\n--\n\n", username); + + for (int i = 0; i < PQntuples(res); i++) + { + resetPQExpBuffer(buf); + makeAlterConfigCommand(conn, PQgetvalue(res, i, 0), + "ROLE", username, NULL, NULL, + buf); + fprintf(OPF, "%s", buf->data); + } + + PQclear(res); + + destroyPQExpBuffer(buf); +} + +/* + * Find a list of database names that match the given patterns. + * See also expand_table_name_patterns() in pg_dump.c + */ +static void +expand_dbname_patterns(PGconn *conn, + SimpleStringList *patterns, + SimpleStringList *names) +{ + PQExpBuffer query; + PGresult *res; + + if (patterns->head == NULL) + return; /* nothing to do */ + + query = createPQExpBuffer(); + + /* + * The loop below runs multiple SELECTs, which might sometimes result in + * duplicate entries in the name list, but we don't care, since all we're + * going to do is test membership of the list. + */ + + for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next) + { + int dotcnt; + + appendPQExpBufferStr(query, + "SELECT datname FROM pg_catalog.pg_database n\n"); + processSQLNamePattern(conn, query, cell->val, false, + false, NULL, "datname", NULL, NULL, NULL, + &dotcnt); + + if (dotcnt > 0) + { + pg_log_error("improper qualified name (too many dotted names): %s", + cell->val); + PQfinish(conn); + exit_nicely(1); + } + + res = executeQuery(conn, query->data); + for (int i = 0; i < PQntuples(res); i++) + { + simple_string_list_append(names, PQgetvalue(res, i, 0)); + } + + PQclear(res); + resetPQExpBuffer(query); + } + + destroyPQExpBuffer(query); +} + +/* + * Dump contents of databases. + */ +static void +dumpDatabases(PGconn *conn) +{ + PGresult *res; + int i; + + /* + * Skip databases marked not datallowconn, since we'd be unable to connect + * to them anyway. This must agree with dropDBs(). + * + * We arrange for template1 to be processed first, then we process other + * DBs in alphabetical order. If we just did them all alphabetically, we + * might find ourselves trying to drop the "postgres" database while still + * connected to it. This makes trying to run the restore script while + * connected to "template1" a bad idea, but there's no fixed order that + * doesn't have some failure mode with --clean. + */ + res = executeQuery(conn, + "SELECT datname " + "FROM pg_database d " + "WHERE datallowconn AND datconnlimit != -2 " + "ORDER BY (datname <> 'template1'), datname"); + + if (PQntuples(res) > 0) + fprintf(OPF, "--\n-- Databases\n--\n\n"); + + for (i = 0; i < PQntuples(res); i++) + { + char *dbname = PQgetvalue(res, i, 0); + const char *create_opts; + int ret; + + /* Skip template0, even if it's not marked !datallowconn. */ + if (strcmp(dbname, "template0") == 0) + continue; + + /* Skip any explicitly excluded database */ + if (simple_string_list_member(&database_exclude_names, dbname)) + { + pg_log_info("excluding database \"%s\"", dbname); + continue; + } + + pg_log_info("dumping database \"%s\"", dbname); + + fprintf(OPF, "--\n-- Database \"%s\" dump\n--\n\n", dbname); + + /* + * We assume that "template1" and "postgres" already exist in the + * target installation. dropDBs() won't have removed them, for fear + * of removing the DB the restore script is initially connected to. If + * --clean was specified, tell pg_dump to drop and recreate them; + * otherwise we'll merely restore their contents. Other databases + * should simply be created. + */ + if (strcmp(dbname, "template1") == 0 || strcmp(dbname, "postgres") == 0) + { + if (output_clean) + create_opts = "--clean --create"; + else + { + create_opts = ""; + /* Since pg_dump won't emit a \connect command, we must */ + fprintf(OPF, "\\connect %s\n\n", dbname); + } + } + else + create_opts = "--create"; + + if (filename) + fclose(OPF); + + ret = runPgDump(dbname, create_opts); + if (ret != 0) + pg_fatal("pg_dump failed on database \"%s\", exiting", dbname); + + if (filename) + { + OPF = fopen(filename, PG_BINARY_A); + if (!OPF) + pg_fatal("could not re-open the output file \"%s\": %m", + filename); + } + } + + PQclear(res); +} + + + +/* + * Run pg_dump on dbname, with specified options. + */ +static int +runPgDump(const char *dbname, const char *create_opts) +{ + PQExpBuffer connstrbuf = createPQExpBuffer(); + PQExpBuffer cmd = createPQExpBuffer(); + int ret; + + appendPQExpBuffer(cmd, "\"%s\" %s %s", pg_dump_bin, + pgdumpopts->data, create_opts); + + /* + * If we have a filename, use the undocumented plain-append pg_dump + * format. + */ + if (filename) + appendPQExpBufferStr(cmd, " -Fa "); + else + appendPQExpBufferStr(cmd, " -Fp "); + + /* + * Append the database name to the already-constructed stem of connection + * string. + */ + appendPQExpBuffer(connstrbuf, "%s dbname=", connstr); + appendConnStrVal(connstrbuf, dbname); + + appendShellString(cmd, connstrbuf->data); + + pg_log_info("running \"%s\"", cmd->data); + + fflush(NULL); + + ret = system(cmd->data); + + destroyPQExpBuffer(cmd); + destroyPQExpBuffer(connstrbuf); + + return ret; +} + +/* + * buildShSecLabels + * + * Build SECURITY LABEL command(s) for a shared object + * + * The caller has to provide object type and identity in two separate formats: + * catalog_name (e.g., "pg_database") and object OID, as well as + * type name (e.g., "DATABASE") and object name (not pre-quoted). + * + * The command(s) are appended to "buffer". + */ +static void +buildShSecLabels(PGconn *conn, const char *catalog_name, Oid objectId, + const char *objtype, const char *objname, + PQExpBuffer buffer) +{ + PQExpBuffer sql = createPQExpBuffer(); + PGresult *res; + + buildShSecLabelQuery(catalog_name, objectId, sql); + res = executeQuery(conn, sql->data); + emitShSecLabels(conn, res, buffer, objtype, objname); + + PQclear(res); + destroyPQExpBuffer(sql); +} + +/* + * Make a database connection with the given parameters. An + * interactive password prompt is automatically issued if required. + * + * If fail_on_error is false, we return NULL without printing any message + * on failure, but preserve any prompted password for the next try. + * + * On success, the global variable 'connstr' is set to a connection string + * containing the options used. + */ +static PGconn * +connectDatabase(const char *dbname, const char *connection_string, + const char *pghost, const char *pgport, const char *pguser, + trivalue prompt_password, bool fail_on_error) +{ + PGconn *conn; + bool new_pass; + const char *remoteversion_str; + int my_version; + const char **keywords = NULL; + const char **values = NULL; + PQconninfoOption *conn_opts = NULL; + static char *password = NULL; + + if (prompt_password == TRI_YES && !password) + password = simple_prompt("Password: ", false); + + /* + * Start the connection. Loop until we have a password if requested by + * backend. + */ + do + { + int argcount = 6; + PQconninfoOption *conn_opt; + char *err_msg = NULL; + int i = 0; + + free(keywords); + free(values); + PQconninfoFree(conn_opts); + + /* + * Merge the connection info inputs given in form of connection string + * and other options. Explicitly discard any dbname value in the + * connection string; otherwise, PQconnectdbParams() would interpret + * that value as being itself a connection string. + */ + if (connection_string) + { + conn_opts = PQconninfoParse(connection_string, &err_msg); + if (conn_opts == NULL) + pg_fatal("%s", err_msg); + + for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++) + { + if (conn_opt->val != NULL && conn_opt->val[0] != '\0' && + strcmp(conn_opt->keyword, "dbname") != 0) + argcount++; + } + + keywords = pg_malloc0((argcount + 1) * sizeof(*keywords)); + values = pg_malloc0((argcount + 1) * sizeof(*values)); + + for (conn_opt = conn_opts; conn_opt->keyword != NULL; conn_opt++) + { + if (conn_opt->val != NULL && conn_opt->val[0] != '\0' && + strcmp(conn_opt->keyword, "dbname") != 0) + { + keywords[i] = conn_opt->keyword; + values[i] = conn_opt->val; + i++; + } + } + } + else + { + keywords = pg_malloc0((argcount + 1) * sizeof(*keywords)); + values = pg_malloc0((argcount + 1) * sizeof(*values)); + } + + if (pghost) + { + keywords[i] = "host"; + values[i] = pghost; + i++; + } + if (pgport) + { + keywords[i] = "port"; + values[i] = pgport; + i++; + } + if (pguser) + { + keywords[i] = "user"; + values[i] = pguser; + i++; + } + if (password) + { + keywords[i] = "password"; + values[i] = password; + i++; + } + if (dbname) + { + keywords[i] = "dbname"; + values[i] = dbname; + i++; + } + keywords[i] = "fallback_application_name"; + values[i] = progname; + i++; + + new_pass = false; + conn = PQconnectdbParams(keywords, values, true); + + if (!conn) + pg_fatal("could not connect to database \"%s\"", dbname); + + if (PQstatus(conn) == CONNECTION_BAD && + PQconnectionNeedsPassword(conn) && + !password && + prompt_password != TRI_NO) + { + PQfinish(conn); + password = simple_prompt("Password: ", false); + new_pass = true; + } + } while (new_pass); + + /* check to see that the backend connection was successfully made */ + if (PQstatus(conn) == CONNECTION_BAD) + { + if (fail_on_error) + pg_fatal("%s", PQerrorMessage(conn)); + else + { + PQfinish(conn); + + free(keywords); + free(values); + PQconninfoFree(conn_opts); + + return NULL; + } + } + + /* + * Ok, connected successfully. Remember the options used, in the form of a + * connection string. + */ + connstr = constructConnStr(keywords, values); + + free(keywords); + free(values); + PQconninfoFree(conn_opts); + + /* Check version */ + remoteversion_str = PQparameterStatus(conn, "server_version"); + if (!remoteversion_str) + pg_fatal("could not get server version"); + server_version = PQserverVersion(conn); + if (server_version == 0) + pg_fatal("could not parse server version \"%s\"", + remoteversion_str); + + my_version = PG_VERSION_NUM; + + /* + * We allow the server to be back to 9.2, and up to any minor release of + * our own major version. (See also version check in pg_dump.c.) + */ + if (my_version != server_version + && (server_version < 90200 || + (server_version / 100) > (my_version / 100))) + { + pg_log_error("aborting because of server version mismatch"); + pg_log_error_detail("server version: %s; %s version: %s", + remoteversion_str, progname, PG_VERSION); + exit_nicely(1); + } + + PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL)); + + return conn; +} + +/* ---------- + * Construct a connection string from the given keyword/value pairs. It is + * used to pass the connection options to the pg_dump subprocess. + * + * The following parameters are excluded: + * dbname - varies in each pg_dump invocation + * password - it's not secure to pass a password on the command line + * fallback_application_name - we'll let pg_dump set it + * ---------- + */ +static char * +constructConnStr(const char **keywords, const char **values) +{ + PQExpBuffer buf = createPQExpBuffer(); + char *connstr; + int i; + bool firstkeyword = true; + + /* Construct a new connection string in key='value' format. */ + for (i = 0; keywords[i] != NULL; i++) + { + if (strcmp(keywords[i], "dbname") == 0 || + strcmp(keywords[i], "password") == 0 || + strcmp(keywords[i], "fallback_application_name") == 0) + continue; + + if (!firstkeyword) + appendPQExpBufferChar(buf, ' '); + firstkeyword = false; + appendPQExpBuffer(buf, "%s=", keywords[i]); + appendConnStrVal(buf, values[i]); + } + + connstr = pg_strdup(buf->data); + destroyPQExpBuffer(buf); + return connstr; +} + +/* + * Run a query, return the results, exit program on failure. + */ +static PGresult * +executeQuery(PGconn *conn, const char *query) +{ + PGresult *res; + + pg_log_info("executing %s", query); + + res = PQexec(conn, query); + if (!res || + PQresultStatus(res) != PGRES_TUPLES_OK) + { + pg_log_error("query failed: %s", PQerrorMessage(conn)); + pg_log_error_detail("Query was: %s", query); + PQfinish(conn); + exit_nicely(1); + } + + return res; +} + +/* + * As above for a SQL command (which returns nothing). + */ +static void +executeCommand(PGconn *conn, const char *query) +{ + PGresult *res; + + pg_log_info("executing %s", query); + + res = PQexec(conn, query); + if (!res || + PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("query failed: %s", PQerrorMessage(conn)); + pg_log_error_detail("Query was: %s", query); + PQfinish(conn); + exit_nicely(1); + } + + PQclear(res); +} + + +/* + * dumpTimestamp + */ +static void +dumpTimestamp(const char *msg) +{ + char buf[64]; + time_t now = time(NULL); + + if (strftime(buf, sizeof(buf), PGDUMP_STRFTIME_FMT, localtime(&now)) != 0) + fprintf(OPF, "-- %s %s\n\n", msg, buf); +} + +/* + * Helper function for rolename_hash hash table. + */ +static uint32 +hash_string_pointer(char *s) +{ + unsigned char *ss = (unsigned char *) s; + + return hash_bytes(ss, strlen(s)); +} |