postgresql/src/bin/pg_dump/pg_dumpall.c

834 lines
20 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* pg_dumpall
*
2003-08-04 04:40:20 +02:00
* 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 <unistd.h>
#ifdef ENABLE_NLS
#include <locale.h>
#endif
#ifndef HAVE_STRDUP
#include "strdup.h"
#endif
#include <errno.h>
2004-06-09 19:05:14 +02:00
#include <time.h>
#include "getopt_long.h"
#ifndef HAVE_OPTRESET
2003-08-04 02:43:34 +02:00
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);
2002-08-28 20:25:05 +02:00
static void dumpUsers(PGconn *conn);
static void dumpGroups(PGconn *conn);
static void dumpCreateDB(PGconn *conn);
2002-08-28 20:25:05 +02:00
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);
2002-08-28 20:25:05 +02:00
2002-09-04 22:31:48 +02:00
static int runPgDump(const char *dbname);
static PGconn *connectDatabase(const char *dbname, const char *pghost, const char *pgport,
2002-09-04 22:31:48 +02:00
const char *pguser, bool require_password);
static PGresult *executeQuery(PGconn *conn, const char *query);
char pg_dump_bin[MAXPGPATH];
PQExpBuffer pgdumpopts;
2002-09-04 22:31:48 +02:00
bool output_clean = false;
bool skip_acls = false;
2002-09-04 22:31:48 +02:00
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;
2002-09-04 22:31:48 +02:00
default:
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
2002-09-04 22:31:48 +02:00
exit(1);
}
}
2002-09-04 22:31:48 +02:00
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);
2002-09-04 22:31:48 +02:00
exit(1);
}
conn = connectDatabase("template1", pghost, pgport, pguser, force_password);
2004-06-05 06:27:48 +02:00
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");
2004-06-05 06:27:48 +02:00
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"));
2002-10-19 00:05:36 +02:00
printf(_(" %s [OPTION]...\n"), progname);
2002-10-19 00:05:36 +02:00
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"));
2002-10-19 00:05:36 +02:00
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"));
2002-10-19 00:05:36 +02:00
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 <pgsql-bugs@postgresql.org>.\n"));
}
/*
* Dump users (but not the user created by initdb).
*/
static void
dumpUsers(PGconn *conn)
{
2002-09-04 22:31:48 +02:00
PGresult *res;
int i;
2002-08-28 20:25:05 +02:00
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,
2003-08-04 02:43:34 +02:00
"SELECT usename, usesysid, passwd, usecreatedb, "
"usesuper, valuntil "
"FROM pg_shadow "
"WHERE usesysid <> (SELECT datdba FROM pg_database WHERE datname = 'template0')");
else
res = executeQuery(conn,
2003-08-04 02:43:34 +02:00
"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();
2002-08-28 20:25:05 +02:00
const char *username;
2002-08-28 20:25:05 +02:00
username = PQgetvalue(res, i, 0);
appendPQExpBuffer(buf, "CREATE USER %s WITH SYSID %s",
2002-08-28 20:25:05 +02:00
fmtId(username),
PQgetvalue(res, i, 1));
if (!PQgetisnull(res, i, 2))
{
appendPQExpBuffer(buf, " PASSWORD ");
appendStringLiteral(buf, PQgetvalue(res, i, 2), true);
}
2002-09-04 22:31:48 +02:00
if (strcmp(PQgetvalue(res, i, 3), "t") == 0)
appendPQExpBuffer(buf, " CREATEDB");
else
appendPQExpBuffer(buf, " NOCREATEDB");
2002-09-04 22:31:48 +02:00
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);
2002-08-28 20:25:05 +02:00
if (server_version >= 70300)
dumpUserConfig(conn, username);
}
PQclear(res);
printf("\n\n");
}
/*
* Dump groups.
*/
static void
dumpGroups(PGconn *conn)
{
2002-09-04 22:31:48 +02:00
PGresult *res;
int i;
2002-08-28 20:25:05 +02:00
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();
2002-09-04 22:31:48 +02:00
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)
{
2002-09-04 22:31:48 +02:00
PGresult *res2;
PQExpBuffer buf2 = createPQExpBuffer();
2002-09-04 22:31:48 +02:00
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)
{
2002-09-04 22:31:48 +02:00
PGresult *res;
int i;
2002-08-28 20:25:05 +02:00
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 "
2003-08-04 02:43:34 +02:00
"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("
2003-08-04 02:43:34 +02:00
"(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, "
2003-08-04 02:43:34 +02:00
"(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;
2002-09-04 22:31:48 +02:00
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;
2002-09-04 22:31:48 +02:00
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");
2002-09-04 22:31:48 +02:00
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);
2002-08-28 20:25:05 +02:00
if (server_version >= 70300)
dumpDatabaseConfig(conn, dbname);
}
PQclear(res);
printf("\n\n");
}
2002-08-28 20:25:05 +02:00
/*
* Dump database-specific configuration
*/
static void
dumpDatabaseConfig(PGconn *conn, const char *dbname)
{
PQExpBuffer buf = createPQExpBuffer();
2002-09-04 22:31:48 +02:00
int count = 1;
2002-08-28 20:25:05 +02:00
2002-09-04 22:31:48 +02:00
for (;;)
2002-08-28 20:25:05 +02:00
{
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();
2002-09-04 22:31:48 +02:00
int count = 1;
2002-08-28 20:25:05 +02:00
2002-09-04 22:31:48 +02:00
for (;;)
2002-08-28 20:25:05 +02:00
{
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)
{
2002-09-04 22:31:48 +02:00
char *pos;
char *mine;
2002-08-28 20:25:05 +02:00
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)
{
2002-09-04 22:31:48 +02:00
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++)
{
2002-09-04 22:31:48 +02:00
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;
2002-09-04 22:31:48 +02:00
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"),
2002-09-04 22:31:48 +02:00
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)
{
2002-09-04 22:31:48 +02:00
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);
}