*
* pgut.c
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 2009-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2017-2019, Postgres Professional
*
*-------------------------------------------------------------------------
*/
#include "pg_probackup.h"
#include "postgres_fe.h"
#include "getopt_long.h"
#include "libpq/libpq-fe.h"
#include "libpq/pqsignal.h"
#include "libpq/pqexpbuffer.h"
#include "common/fe_memutils.h"
#include <time.h>
#include "pgut.h"
#include "logger.h"
#include "file.h"
bool prompt_password = true;
static PGcancel *volatile cancel_conn = NULL;
bool interrupted = false;
bool in_cleanup = false;
bool in_password = false;
static void init_cancel_handler(void);
static void on_before_exec(PGconn *conn, PGcancel *thread_cancel_conn);
static void on_after_exec(PGcancel *thread_cancel_conn);
static void on_interrupt(void);
void on_cleanup(void);
static pqsigfunc oldhandler = NULL;
static char ** pgut_pgfnames(const char *path, bool strict);
static void pgut_pgfnames_cleanup(char **filenames);
void discard_response(PGconn *conn);
static PGresult *pgut_async_query(PGconn *conn, const char *query,
int nParams, const char **params,
bool text_result);
extern char* inc_dbport(const char* dbport);
#define DefaultTty ""
#define DefaultOption ""
#define DefaultAuthtype ""
#ifdef USE_SSL
#define DefaultSSLMode "prefer"
#else
#define DefaultSSLMode "disable"
#endif
#define DefaultGSSMode "prefer"
#else
#define DefaultGSSMode "disable"
#endif may be removed in the future*/
typedef struct _internalPQconninfoOption
{
const char *keyword;
const char *envvar;
const char *compiled;
char *val;
const char *label;
const char *dispchar;
int dispsize;
off_t connofs;
} internalPQconninfoOption;
static const internalPQconninfoOption PQconninfoOptions[] = {
* * "authtype" is no longer used, so mark it "don't show". We keep it in
* * the array so as not to reject conninfo strings from old apps that might
* * still try to set it.
* */
{(const char *)"authtype", (const char *)"PGAUTHTYPE", (const char *)DefaultAuthtype, NULL,
(const char *)"Database-Authtype", (const char *)"D", 20, -1},
{(const char *)"service", (const char *)"PGSERVICE", NULL, NULL,
(const char *)"Database-Service", (const char *)"", 20, -1},
{(const char *)"user", (const char *)"PGUSER", NULL, NULL,
(const char *)"Database-User", (const char *)"", 20,
offsetof(struct pg_conn, pguser)},
{(const char *)"password", (const char *)"PGPASSWORD", NULL, NULL,
(const char *)"Database-Password", (const char *)"*", 20,
offsetof(struct pg_conn, pgpass)},
{(const char *)"connect_timeout", (const char *)"PGCONNECT_TIMEOUT", NULL, NULL,
(const char *)"Connect-timeout", (const char *)"", 10,
offsetof(struct pg_conn, connect_timeout)},
{(const char *)"dbname", (const char *)"PGDATABASE", NULL, NULL,
(const char *)"Database-Name", (const char *)"", 20,
offsetof(struct pg_conn, dbName)},
{(const char *)"host", (const char *)"PGHOST", NULL, NULL,
(const char *)"Database-Host", (const char *)"", 40,
offsetof(struct pg_conn, pghost)},
{(const char *)"hostaddr", (const char *)"PGHOSTADDR", NULL, NULL,
(const char *)"Database-Host-IP-Address", (const char *)"", 45,
offsetof(struct pg_conn, pghostaddr)},
{(const char *)"port", (const char *)"PGPORT", (const char *)DEF_PGPORT_STR, NULL,
(const char *)"Database-Port", (const char *)"", 6,
offsetof(struct pg_conn, pgport)},
{(const char *)"client_encoding", (const char *)"PGCLIENTENCODING", NULL, NULL,
(const char *)"Client-Encoding", (const char *)"", 10,
offsetof(struct pg_conn, client_encoding_initial)},
* * "tty" is no longer used either, but keep it present for backwards
* * compatibility.
* */
{(const char *)"tty", (const char *)"PGTTY", (const char *)DefaultTty, NULL,
(const char *)"Backend-Debug-TTY", (const char *)"D", 40,
offsetof(struct pg_conn, pgtty)},
{(const char *)"options", (const char *)"PGOPTIONS", (const char *)DefaultOption, NULL,
(const char *)"Backend-Options", (const char *)"", 40,
offsetof(struct pg_conn, pgoptions)},
{(const char *)"application_name", (const char *)"PGAPPNAME", NULL, NULL,
(const char *)"Application-Name", (const char *)"", 64,
offsetof(struct pg_conn, appname)},
{(const char *)"fallback_application_name", NULL, NULL, NULL,
(const char *)"Fallback-Application-Name", (const char *)"", 64,
offsetof(struct pg_conn, fbappname)},
{(const char *)"keepalives", NULL, NULL, NULL,
(const char *)"TCP-Keepalives", (const char *)"", 1,
offsetof(struct pg_conn, keepalives)},
{(const char *)"keepalives_idle", NULL, NULL, NULL,
(const char *)"TCP-Keepalives-Idle", (const char *)"", 10,
offsetof(struct pg_conn, keepalives_idle)},
{(const char *)"keepalives_interval", NULL, NULL, NULL,
(const char *)"TCP-Keepalives-Interval", (const char *)"", 10,
offsetof(struct pg_conn, keepalives_interval)},
{(const char *)"keepalives_count", NULL, NULL, NULL,
(const char *)"TCP-Keepalives-Count", (const char *)"", 10,
offsetof(struct pg_conn, keepalives_count)},
* * ssl options are allowed even without client SSL support because the
* * client can still handle SSL modes "disable" and "allow". Other
* * parameters have no effect on non-SSL connections, so there is no reason
* * to exclude them since none of them are mandatory.
* */
{(const char *)"sslmode", (const char *)"PGSSLMODE", (const char *)DefaultSSLMode, NULL,
(const char *)"SSL-Mode", (const char *)"", 12,
offsetof(struct pg_conn, sslmode)},
{(const char *)"sslcompression", (const char *)"PGSSLCOMPRESSION", (const char *)"0", NULL,
(const char *)"SSL-Compression", (const char *)"", 1,
offsetof(struct pg_conn, sslcompression)},
{(const char *)"sslcert", (const char *)"PGSSLCERT", NULL, NULL,
(const char *)"SSL-Client-Cert", (const char *)"", 64,
offsetof(struct pg_conn, sslcert)},
{(const char *)"sslkey", (const char *)"PGSSLKEY", NULL, NULL,
(const char *)"SSL-Client-Key", (const char *)"", 64,
offsetof(struct pg_conn, sslkey)},
{(const char *)"sslrootcert", (const char *)"PGSSLROOTCERT", NULL, NULL,
(const char *)"SSL-Root-Certificate", (const char *)"", 64,
offsetof(struct pg_conn, sslrootcert)},
{(const char *)"sslcrl", (const char *)"PGSSLCRL", NULL, NULL,
(const char *)"SSL-Revocation-List", (const char *)"", 64,
offsetof(struct pg_conn, sslcrl)},
{(const char *)"requirepeer", (const char *)"PGREQUIREPEER", NULL, NULL,
(const char *)"Require-Peer", (const char *)"", 10,
offsetof(struct pg_conn, requirepeer)},
* * As with SSL, all GSS options are exposed even in builds that don't have
* * support.
* */
#ifdef KRB5
{(const char *)"krbsrvname", (const char *)"PGKRBSRVNAME", (const char *)PG_KRB_SRVNAM, NULL,
(const char *)"Kerberos-service-name", (const char *)"", 20,
offsetof(struct pg_conn, krbsrvname)},
#endif
{(const char *)"replication", NULL, NULL, NULL,
(const char *)"Replication", (const char *)"D", 5,
offsetof(struct pg_conn, replication)},
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
};
* * Build a working copy of the constant PQconninfoOptions array.
* */
static PQconninfoOption *
conninfo_init(PQExpBuffer errorMessage)
{
PQconninfoOption *options;
PQconninfoOption *opt_dest;
const internalPQconninfoOption *cur_opt;
size_t size = sizeof(PQconninfoOption) * sizeof(PQconninfoOptions) / sizeof(PQconninfoOptions[0]);
errno_t rc = 0;
* * Get enough memory for all options in PQconninfoOptions, even if some
* * end up being filtered out.
* */
options = (PQconninfoOption *) malloc(size);
if (options == NULL)
{
printfPQExpBuffer(errorMessage,
libpq_gettext("out of memory\n"));
return NULL;
}
opt_dest = options;
for (cur_opt = PQconninfoOptions; cur_opt->keyword; cur_opt++)
{
rc = memcpy_s(opt_dest, size, cur_opt, sizeof(PQconninfoOption));
securec_check_c(rc, "\0", "\0");
opt_dest++;
size = size - sizeof(PQconninfoOption);
}
rc = memset_s(opt_dest, size, 0, sizeof(PQconninfoOption));
securec_check(rc, "\0", "\0");
return options;
}
static bool
get_hexdigit(char digit, int *value)
{
if ('0' <= digit && digit <= '9')
*value = digit - '0';
else if ('A' <= digit && digit <= 'F')
*value = digit - 'A' + 10;
else if ('a' <= digit && digit <= 'f')
*value = digit - 'a' + 10;
else
return false;
return true;
}
static char *
conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
{
char *buf;
char *p;
const char *q = str;
buf = (char *)malloc(strlen(str) + 1);
if (buf == NULL)
{
printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
return NULL;
}
p = buf;
for (;;)
{
if (*q != '%')
{
if (!(*(p++) = *(q++)))
break;
}
else
{
int hi;
int lo;
int c;
++q;
* * Possible EOL will be caught by the first call to
* * get_hexdigit(), so we never dereference an invalid q pointer.
* */
if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo)))
{
printfPQExpBuffer(errorMessage,
libpq_gettext("invalid percent-encoded token: \"%s\"\n"),
str);
free(buf);
return NULL;
}
c = (hi << 4) | lo;
if (c == 0)
{
printfPQExpBuffer(errorMessage,
libpq_gettext("forbidden value %%00 in percent-encoded value: \"%s\"\n"),
str);
free(buf);
return NULL;
}
*(p++) = c;
}
}
return buf;
}
static PQconninfoOption *
conninfo_find(PQconninfoOption *connOptions, const char *keyword)
{
PQconninfoOption *option;
for (option = connOptions; option->keyword != NULL; option++)
{
if (strcmp(option->keyword, keyword) == 0)
return option;
}
return NULL;
}
static PQconninfoOption *
conninfo_storeval(PQconninfoOption *connOptions,
const char *keyword, const char *value,
PQExpBuffer errorMessage, bool ignoreMissing,
bool uri_decode)
{
PQconninfoOption *option;
char *value_copy;
* * For backwards compatibility, requiressl=1 gets translated to
* * sslmode=require, and requiressl=0 gets translated to sslmode=prefer
* * (which is the default for sslmode).
* */
if (strcmp(keyword, "requiressl") == 0)
{
keyword = "sslmode";
if (value[0] == '1')
value = "require";
else
value = "prefer";
}
option = conninfo_find(connOptions, keyword);
if (option == NULL)
{
if (!ignoreMissing)
printfPQExpBuffer(errorMessage,
libpq_gettext("invalid connection option \"%s\"\n"),
keyword);
return NULL;
}
if (uri_decode)
{
value_copy = conninfo_uri_decode(value, errorMessage);
if (value_copy == NULL)
return NULL;
}
else
{
value_copy = strdup(value);
if (value_copy == NULL)
{
printfPQExpBuffer(errorMessage, libpq_gettext("out of memory\n"));
return NULL;
}
}
if (option->val)
free(option->val);
option->val = value_copy;
return option;
}
* * Return the connection options used for the connection
* */
PQconninfoOption *
PQconninfo(PGconn *conn)
{
PQExpBufferData errorBuf;
PQconninfoOption *connOptions;
if (conn == NULL)
return NULL;
initPQExpBuffer(&errorBuf);
if (PQExpBufferDataBroken(errorBuf))
return NULL;
connOptions = conninfo_init(&errorBuf);
if (connOptions != NULL)
{
const internalPQconninfoOption *option;
for (option = PQconninfoOptions; option->keyword; option++)
{
char **connmember;
if (option->connofs < 0)
continue;
connmember = (char **) ((char *) conn + option->connofs);
if (*connmember)
conninfo_storeval(connOptions, option->keyword, *connmember,
&errorBuf, true, false);
}
}
termPQExpBuffer(&errorBuf);
return connOptions;
}
void
pgut_init(void)
{
init_cancel_handler();
}
* Ask the user for a password; 'username' is the username the
* password is for, if one has been explicitly specified.
* Set malloc'd string to the global variable 'password'.
*/
static void
prompt_for_password(const char *username)
{
in_password = true;
int nRet = 0;
if (password)
{
free(password);
password = NULL;
}
#if PG_VERSION_NUM >= 100000
password = (char *) pgut_malloc(sizeof(char) * 100 + 1);
if (username == NULL)
simple_prompt("Password: ", password, 100, false);
else
{
char message[256];
nRet = snprintf_s(message, lengthof(message), lengthof(message) - 1, "Password for user %s: ", username);
securec_check_ss_c(nRet, "\0", "\0");
simple_prompt(message, password, 100, false);
}
#else
if (username == NULL)
password = simple_prompt("Password: ", 100, false);
else
{
char message[256];
nRet = snprintf_s(message, lengthof(message), lengthof(message) - 1, "Password for user %s: ", username);
securec_check_ss_c(nRet, "\0", "\0");
password = simple_prompt(message, 100, false);
}
#endif
in_password = false;
}
PGconn* pgut_connect(const char *host, const char *port,
const char *dbname, const char *username)
{
PGconn *conn;
int argcount = 8;
* host, user, port, password */
int i;
const char **keywords;
const char **values;
errno_t rc = EOK;
char rwtimeoutStr[12] = {0};
if (interrupted && !in_cleanup)
elog(ERROR, "interrupted");
i = 0;
keywords = (const char**)pg_malloc0((argcount + 1) * sizeof(*keywords));
values = (const char**)pg_malloc0((argcount + 1) * sizeof(*values));
keywords[i] = "dbname";
values[i] = dbname;
i++;
keywords[i] = "fallback_application_name";
values[i] = PROGRAM_NAME;
i++;
keywords[i] = "connect_timeout";
values[i] = "120";
i++;
rc = snprintf_s(rwtimeoutStr, sizeof(rwtimeoutStr), sizeof(rwtimeoutStr) - 1, "%d",
rw_timeout ? rw_timeout : 120);
securec_check_ss_c(rc, "", "");
keywords[i] = "rw_timeout";
values[i] = rwtimeoutStr;
i++;
if (host)
{
keywords[i] = "host";
values[i] = host;
i++;
}
if (username)
{
keywords[i] = "user";
values[i] = username;
i++;
}
if (port)
{
keywords[i] = "port";
values[i] = port;
i++;
}
if (password)
{
keywords[i] = "password";
values[i] = password;
}
else
{
keywords[i] = NULL;
values[i] = NULL;
}
for (;;)
{
errno_t err = EOK;
size_t size = (argcount + 1) * sizeof(*values);
conn = PQconnectdbParams(keywords, values, true);
if (PQstatus(conn) == CONNECTION_OK)
{
pgut_atexit_push(pgut_disconnect_callback, conn);
err = memset_s(values, size, 0, size);
securec_check_c(err, "\0", "\0");
pfree(values);
pfree(keywords);
return conn;
}
if (conn && PQconnectionNeedsPassword(conn) && prompt_password)
{
PQfinish(conn);
prompt_for_password(username);
if (interrupted)
elog(ERROR, "interrupted");
if (password == NULL || password[0] == '\0')
elog(ERROR, "no password supplied");
keywords[i] = "password";
values[i] = password;
continue;
}
elog(ERROR, "could not connect to database %s: %s",
dbname, PQerrorMessage(conn));
PQfinish(conn);
err = memset_s(values, size, 0, size);
securec_check_c(err, "\0", "\0");
pfree(values);
pfree(keywords);
return NULL;
}
}
PGconn* pgut_connect_replication(const char *host, const char *port,
const char *dbname, const char *username)
{
PGconn *tmpconn;
int argcount = 9;
* host, user, port, password */
int i;
const char **keywords;
const char **values;
errno_t rc = EOK;
char rwtimeoutStr[12] = {0};
const char *malloc_port = NULL;
if (interrupted && !in_cleanup)
elog(ERROR, "interrupted");
i = 0;
keywords = (const char**)pg_malloc0((argcount + 1) * sizeof(*keywords));
values = (const char**)pg_malloc0((argcount + 1) * sizeof(*values));
keywords[i] = "dbname";
values[i] = "replication";
i++;
keywords[i] = "replication";
values[i] = "true";
i++;
keywords[i] = "fallback_application_name";
values[i] = PROGRAM_NAME;
i++;
keywords[i] = "connect_timeout";
values[i] = "120";
i++;
rc = snprintf_s(rwtimeoutStr, sizeof(rwtimeoutStr), sizeof(rwtimeoutStr) - 1, "%d",
rw_timeout ? rw_timeout : 120);
securec_check_ss_c(rc, "", "");
keywords[i] = "rw_timeout";
values[i] = rwtimeoutStr;
i++;
if (host)
{
keywords[i] = "host";
values[i] = host;
i++;
}
if (username)
{
keywords[i] = "user";
values[i] = username;
i++;
}
if (port)
{
keywords[i] = "port";
values[i] = inc_dbport(port);
malloc_port = values[i];
i++;
}
if (password)
{
keywords[i] = "password";
values[i] = password;
}
else
{
keywords[i] = NULL;
values[i] = NULL;
}
for (;;)
{
tmpconn = PQconnectdbParams(keywords, values, (int)true);
if (PQstatus(tmpconn) == CONNECTION_OK)
{
free(values);
free(keywords);
if (malloc_port)
{
free((void *)malloc_port);
}
return tmpconn;
}
if (tmpconn && PQconnectionNeedsPassword(tmpconn) && prompt_password)
{
PQfinish(tmpconn);
prompt_for_password(username);
keywords[i] = "password";
values[i] = password;
continue;
}
elog(ERROR, "could not connect to database %s: %s",
dbname, PQerrorMessage(tmpconn));
PQfinish(tmpconn);
free(values);
free(keywords);
if (malloc_port)
{
free((void *)malloc_port);
}
return NULL;
}
}
void
pgut_disconnect(PGconn *conn)
{
pgut_atexit_pop(pgut_disconnect_callback, conn);
if (conn)
PQfinish(conn);
}
PGresult *
pgut_execute_parallel(PGconn* conn,
PGcancel* thread_cancel_conn, const char *query,
int nParams, const char **params,
bool text_result, bool ok_error, bool async)
{
PGresult *res;
if (interrupted && !in_cleanup)
elog(ERROR, "interrupted");
if (logger_config.log_level_console <= VERBOSE ||
logger_config.log_level_file <= VERBOSE)
{
int i;
if (strchr(query, '\n'))
elog(VERBOSE, "(query)\n%s", query);
else
elog(VERBOSE, "(query) %s", query);
for (i = 0; i < nParams; i++)
elog(VERBOSE, "\t(param:%d) = %s", i, params[i] ? params[i] : "(null)");
}
if (conn == NULL)
{
elog(ERROR, "not connected");
return NULL;
}
if (async)
{
res = pgut_async_query(conn, query, nParams, params, text_result);
}
else
{
if (nParams == 0)
res = PQexec(conn, query);
else
res = PQexecParams(conn, query, nParams, NULL, params, NULL, NULL,
* Specify zero to obtain results in text format,
* or one to obtain results in binary format.
*/
(text_result) ? 0 : 1);
}
switch (PQresultStatus(res))
{
case PGRES_TUPLES_OK:
case PGRES_COMMAND_OK:
case PGRES_COPY_IN:
break;
default:
if (ok_error && PQresultStatus(res) == PGRES_FATAL_ERROR)
break;
elog(ERROR, "query failed: %squery was: %s",
PQerrorMessage(conn), query);
break;
}
return res;
}
static PGresult *pgut_async_query(PGconn *conn, const char *query,
int nParams, const char **params,
bool text_result)
{
discard_response(conn);
if (nParams == 0)
PQsendQuery(conn, query);
else
PQsendQueryParams(conn, query, nParams, NULL, params, NULL, NULL,
* Specify zero to obtain results in text format,
* or one to obtain results in binary format.
*/
(text_result) ? 0 : 1);
for (;;)
{
if (interrupted)
{
pgut_cancel(conn);
pgut_disconnect(conn);
elog(ERROR, "interrupted");
}
if (!PQconsumeInput(conn))
elog(ERROR, "query failed: %s query was: %s",
PQerrorMessage(conn), query);
if (!PQisBusy(conn))
break;
usleep(10000);
}
return PQgetResult(conn);
}
PGresult *
pgut_execute(PGconn* conn, const char *query, int nParams, const char **params)
{
return pgut_execute_extended(conn, query, nParams, params, true, false);
}
PGresult *
pgut_execute_extended(PGconn* conn, const char *query, int nParams,
const char **params, bool text_result, bool ok_error)
{
PGresult *res;
ExecStatusType res_status;
if (interrupted && !in_cleanup)
elog(ERROR, "interrupted");
if (logger_config.log_level_console <= VERBOSE ||
logger_config.log_level_file <= VERBOSE)
{
int i;
if (strchr(query, '\n'))
elog(VERBOSE, "(query)\n%s", query);
else
elog(VERBOSE, "(query) %s", query);
for (i = 0; i < nParams; i++)
elog(VERBOSE, "\t(param:%d) = %s", i, params[i] ? params[i] : "(null)");
}
if (conn == NULL)
{
elog(ERROR, "not connected");
return NULL;
}
on_before_exec(conn, NULL);
if (nParams == 0)
res = PQexec(conn, query);
else
res = PQexecParams(conn, query, nParams, NULL, params, NULL, NULL,
* Specify zero to obtain results in text format,
* or one to obtain results in binary format.
*/
(text_result) ? 0 : 1);
on_after_exec(NULL);
res_status = PQresultStatus(res);
switch (res_status)
{
case PGRES_TUPLES_OK:
case PGRES_COMMAND_OK:
case PGRES_COPY_IN:
break;
default:
if (ok_error && res_status == PGRES_FATAL_ERROR)
break;
elog(ERROR, "query failed: %squery was: %s",
PQerrorMessage(conn), query);
break;
}
return res;
}
bool
pgut_send(PGconn* conn, const char *query, int nParams, const char **params, int elevel)
{
int res;
if (interrupted && !in_cleanup)
elog(ERROR, "interrupted");
if (logger_config.log_level_console <= VERBOSE ||
logger_config.log_level_file <= VERBOSE)
{
int i;
if (strchr(query, '\n'))
elog(VERBOSE, "(query)\n%s", query);
else
elog(VERBOSE, "(query) %s", query);
for (i = 0; i < nParams; i++)
elog(VERBOSE, "\t(param:%d) = %s", i, params[i] ? params[i] : "(null)");
}
if (conn == NULL)
{
elog(elevel, "not connected");
return false;
}
if (nParams == 0)
res = PQsendQuery(conn, query);
else
res = PQsendQueryParams(conn, query, nParams, NULL, params, NULL, NULL, 0);
if (res != 1)
{
elog(elevel, "query failed: %squery was: %s",
PQerrorMessage(conn), query);
return false;
}
return true;
}
void
pgut_cancel(PGconn* conn)
{
PGcancel *cancel_conn = PQgetCancel(conn);
char errbuf[256];
if (cancel_conn != NULL)
{
if (PQcancel_timeout(cancel_conn, errbuf, sizeof(errbuf),10))
elog(WARNING, "Cancel request sent");
else
elog(WARNING, "Cancel request failed");
}
if (cancel_conn)
PQfreeCancel(cancel_conn);
}
int
pgut_wait(int num, PGconn *connections[], struct timeval *timeout)
{
while (!interrupted)
{
int i;
fd_set mask;
int maxsock;
FD_ZERO(&mask);
maxsock = -1;
for (i = 0; i < num; i++)
{
int sock;
if (connections[i] == NULL)
continue;
sock = PQsocket(connections[i]);
if (sock >= 0)
{
FD_SET(sock, &mask);
if (maxsock < sock)
maxsock = sock;
}
}
if (maxsock == -1)
{
errno = ENOENT;
return -1;
}
i = wait_for_sockets(maxsock + 1, &mask, timeout);
if (i == 0)
break;
for (i = 0; i < num; i++)
{
if (connections[i] && FD_ISSET(PQsocket(connections[i]), &mask))
{
PQconsumeInput(connections[i]);
if (PQisBusy(connections[i]))
continue;
return i;
}
}
}
errno = EINTR;
return -1;
}
#ifdef WIN32
static CRITICAL_SECTION cancelConnLock;
#endif
* on_before_exec
*
* Set cancel_conn to point to the current database connection.
*/
static void
on_before_exec(PGconn *conn, PGcancel *thread_cancel_conn)
{
PGcancel *old;
if (in_cleanup)
return;
#ifdef WIN32
EnterCriticalSection(&cancelConnLock);
#endif
if (thread_cancel_conn)
{
old = thread_cancel_conn;
thread_cancel_conn = NULL;
if (old != NULL)
PQfreeCancel(old);
thread_cancel_conn = PQgetCancel(conn);
}
else
{
old = cancel_conn;
cancel_conn = NULL;
if (old != NULL)
PQfreeCancel(old);
cancel_conn = PQgetCancel(conn);
}
#ifdef WIN32
LeaveCriticalSection(&cancelConnLock);
#endif
}
* on_after_exec
*
* Free the current cancel connection, if any, and set to NULL.
*/
static void
on_after_exec(PGcancel *thread_cancel_conn)
{
PGcancel *old;
if (in_cleanup)
return;
#ifdef WIN32
EnterCriticalSection(&cancelConnLock);
#endif
if (thread_cancel_conn)
{
old = thread_cancel_conn;
thread_cancel_conn = NULL;
if (old != NULL)
PQfreeCancel(old);
}
else
{
old = cancel_conn;
cancel_conn = NULL;
if (old != NULL)
PQfreeCancel(old);
}
#ifdef WIN32
LeaveCriticalSection(&cancelConnLock);
#endif
}
* Handle interrupt signals by cancelling the current command.
*/
static void
on_interrupt(void)
{
int save_errno = errno;
char errbuf[256];
interrupted = true;
* User prompts password, call on_cleanup() byhand. Unless we do that we will
* get stuck forever until a user enters a password.
*/
if (in_password)
{
on_cleanup();
pqsignal(SIGINT, oldhandler);
kill(0, SIGINT);
}
if (!in_cleanup && cancel_conn != NULL &&
PQcancel(cancel_conn, errbuf, sizeof(errbuf)))
{
elog(WARNING, "Cancel request sent");
}
errno = save_errno;
}
typedef struct pgut_atexit_item pgut_atexit_item;
struct pgut_atexit_item
{
pgut_atexit_callback callback;
void *userdata;
pgut_atexit_item *next;
};
static pgut_atexit_item *pgut_atexit_stack = NULL;
void
pgut_disconnect_callback(bool fatal, void *userdata)
{
PGconn *conn = (PGconn *) userdata;
if (conn)
pgut_disconnect(conn);
}
void
pgut_atexit_push(pgut_atexit_callback callback, void *userdata)
{
pgut_atexit_item *item = pgut_new(pgut_atexit_item);
AssertArg(callback != NULL);
item->callback = callback;
item->userdata = userdata;
item->next = pgut_atexit_stack;
pgut_atexit_stack = item;
}
void
pgut_atexit_pop(pgut_atexit_callback callback, void *userdata)
{
pgut_atexit_item *item;
pgut_atexit_item **prev;
for (item = pgut_atexit_stack, prev = &pgut_atexit_stack;
item;
prev = &item->next, item = item->next)
{
if (item->callback == callback && item->userdata == userdata)
{
*prev = item->next;
free(item);
break;
}
}
}
static void
call_atexit_callbacks(bool fatal)
{
pgut_atexit_item *item;
pgut_atexit_item *next;
for (item = pgut_atexit_stack; item; item = next){
next = item->next;
item->callback(fatal, item->userdata);
}
}
void
on_cleanup(void)
{
in_cleanup = true;
interrupted = false;
call_atexit_callbacks(false);
}
void *
pgut_malloc(size_t size)
{
char *ret;
if (size == 0)
{
elog(ERROR, "could not allocate 0 byte of memory");
return NULL;
}
if ((ret = (char *)malloc(size)) == NULL)
elog(ERROR, "could not allocate memory (%lu bytes): %s",
(unsigned long) size, strerror(errno));
return ret;
}
void *
pgut_realloc(void *p, size_t oldSize, size_t size)
{
char *ret;
int err = 0;
if (size == 0 || size < oldSize)
{
elog(ERROR, "could not re-allocate memory(old size %lu, new size %lu)", oldSize, size);
return NULL;
}
if ((ret = (char *)pgut_malloc(size)) == NULL)
elog(ERROR, "could not re-allocate memory (%lu bytes): %s",
(unsigned long) size, strerror(errno));
if (oldSize != 0) {
err = memcpy_s(ret, size, p, oldSize);
securec_check_c(err, "\0", "\0");
pfree(p);
}
return ret;
}
char *
pgut_strdup(const char *str)
{
char *ret;
if (str == NULL)
return NULL;
if ((ret = strdup(str)) == NULL)
elog(ERROR, "could not duplicate string \"%s\": %s",
str, strerror(errno));
return ret;
}
FILE *
pgut_fopen(const char *path, const char *mode, bool missing_ok)
{
FILE *fp;
if ((fp = fio_open_stream(path, FIO_BACKUP_HOST)) == NULL)
{
if (missing_ok && errno == ENOENT)
return NULL;
elog(ERROR, "could not open file \"%s\": %s",
path, strerror(errno));
}
return fp;
}
#ifdef WIN32
static int select_win32(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval * timeout);
#define select select_win32
#endif
int
wait_for_socket(int sock, struct timeval *timeout)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
return wait_for_sockets(sock + 1, &fds, timeout);
}
int
wait_for_sockets(int nfds, fd_set *fds, struct timeval *timeout)
{
int i;
for (;;)
{
i = select(nfds, fds, NULL, NULL, timeout);
if (i < 0)
{
if (interrupted)
elog(ERROR, "interrupted");
else if (errno != EINTR)
elog(ERROR, "select failed: %s", strerror(errno));
}
else
return i;
}
}
#ifndef WIN32
static void
handle_interrupt(SIGNAL_ARGS)
{
on_interrupt();
}
static void
init_cancel_handler(void)
{
oldhandler = pqsignal(SIGINT, handle_interrupt);
pqsignal(SIGQUIT, handle_interrupt);
pqsignal(SIGTERM, handle_interrupt);
}
#else
* Console control handler for Win32. Note that the control handler will
* execute on a *different thread* than the main one, so we need to do
* proper locking around those structures.
*/
static BOOL WINAPI
consoleHandler(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_C_EVENT ||
dwCtrlType == CTRL_BREAK_EVENT)
{
EnterCriticalSection(&cancelConnLock);
on_interrupt();
LeaveCriticalSection(&cancelConnLock);
return TRUE;
}
else
return FALSE;
}
static void
init_cancel_handler(void)
{
InitializeCriticalSection(&cancelConnLock);
SetConsoleCtrlHandler(consoleHandler, TRUE);
}
int
sleep(unsigned int seconds)
{
Sleep(seconds * 1000);
return 0;
}
int
usleep(unsigned int usec)
{
Sleep((usec + 999) / 1000);
return 0;
}
#undef select
static int
select_win32(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval * timeout)
{
struct timeval remain;
if (timeout != NULL)
remain = *timeout;
else
{
remain.tv_usec = 0;
remain.tv_sec = LONG_MAX;
}
while (remain.tv_sec > 0 || remain.tv_usec > 0)
{
int ret;
struct timeval onesec;
if (remain.tv_sec > 0)
{
onesec.tv_sec = 1;
onesec.tv_usec = 0;
remain.tv_sec -= 1;
}
else
{
onesec.tv_sec = 0;
onesec.tv_usec = remain.tv_usec;
remain.tv_usec = 0;
}
ret = select(nfds, readfds, writefds, exceptfds, &onesec);
if (ret != 0)
{
return ret;
}
else if (interrupted)
{
errno = EINTR;
return 0;
}
}
return 0;
}
#endif
void
discard_response(PGconn *conn)
{
PGresult *res;
do
{
res = PQgetResult(conn);
if (res)
PQclear(res);
} while (res);
}
* pgfnames
*
* return a list of the names of objects in the argument directory. Caller
* must call pgfnames_cleanup later to free the memory allocated by this
* function.
*/
char **
pgut_pgfnames(const char *path, bool strict)
{
DIR *dir;
struct dirent *file;
char **filenames;
int numnames = 0;
int fnsize = 200;
dir = opendir(path);
if (dir == NULL)
{
elog(strict ? ERROR : WARNING, "could not open directory \"%s\": %m", path);
return NULL;
}
filenames = (char **) palloc(fnsize * sizeof(char *));
while (errno = 0, (file = readdir(dir)) != NULL)
{
if (strcmp(file->d_name, ".") != 0 && strcmp(file->d_name, "..") != 0)
{
if (numnames + 1 >= fnsize)
{
fnsize *= 2;
filenames = (char **) gs_repalloc(filenames,
fnsize * sizeof(char *));
}
filenames[numnames++] = gs_pstrdup(file->d_name);
}
}
if (errno)
{
elog(strict ? ERROR : WARNING, "could not read directory \"%s\": %m", path);
for (int i = 0; i < numnames; i++) {
pfree(filenames[i]);
}
pfree(filenames);
(void)closedir(dir);
return NULL;
}
filenames[numnames] = NULL;
if (closedir(dir))
{
elog(strict ? ERROR : WARNING, "could not close directory \"%s\": %m", path);
for (int i = 0; i < numnames; i++) {
pfree(filenames[i]);
}
pfree(filenames);
return NULL;
}
return filenames;
}
* pgfnames_cleanup
*
* deallocate memory used for filenames
*/
void
pgut_pgfnames_cleanup(char **filenames)
{
char **fn;
for (fn = filenames; *fn; fn++)
pfree(*fn);
pfree(filenames);
}
bool
pgut_rmtree(const char *path, bool rmtopdir, bool strict)
{
bool result = true;
char pathbuf[MAXPGPATH];
char **filenames;
char **filename;
struct stat statbuf;
int nRet = 0;
* we copy all the names out of the directory before we start modifying
* it.
*/
filenames = pgut_pgfnames(path, strict);
if (filenames == NULL)
return false;
for (filename = filenames; *filename; filename++)
{
nRet = snprintf_s(pathbuf, MAXPGPATH, MAXPGPATH - 1, "%s/%s", path, *filename);
securec_check_ss_c(nRet, "\0", "\0");
if (lstat(pathbuf, &statbuf) != 0)
{
elog(strict ? ERROR : WARNING, "could not stat file or directory \"%s\": %m", pathbuf);
result = false;
break;
}
if (S_ISDIR(statbuf.st_mode))
{
if (!pgut_rmtree(pathbuf, true, strict))
{
result = false;
break;
}
}
else
{
if (unlink(pathbuf) != 0)
{
elog(strict ? ERROR : WARNING, "could not remove file or directory \"%s\": %m", pathbuf);
result = false;
break;
}
}
}
if (rmtopdir)
{
if (rmdir(path) != 0)
{
elog(strict ? ERROR : WARNING, "could not remove file or directory \"%s\": %m", path);
result = false;
}
}
pgut_pgfnames_cleanup(filenames);
return result;
}