mirror of
https://github.com/postgrespro/pg_probackup.git
synced 2024-12-13 11:53:59 +02:00
1694 lines
32 KiB
C
1694 lines
32 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pgut.c
|
|
*
|
|
* Portions Copyright (c) 2009-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
|
* Portions Copyright (c) 2017-2017, Postgres Professional
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres_fe.h"
|
|
#include "libpq/pqsignal.h"
|
|
|
|
#include "getopt_long.h"
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "logger.h"
|
|
#include "pgut.h"
|
|
|
|
/* old gcc doesn't have LLONG_MAX. */
|
|
#ifndef LLONG_MAX
|
|
#if defined(HAVE_LONG_INT_64) || !defined(HAVE_LONG_LONG_INT_64)
|
|
#define LLONG_MAX LONG_MAX
|
|
#else
|
|
#define LLONG_MAX INT64CONST(0x7FFFFFFFFFFFFFFF)
|
|
#endif
|
|
#endif
|
|
|
|
const char *PROGRAM_NAME = NULL;
|
|
|
|
const char *pgut_dbname = NULL;
|
|
const char *host = NULL;
|
|
const char *port = NULL;
|
|
const char *username = NULL;
|
|
char *password = NULL;
|
|
bool prompt_password = true;
|
|
|
|
/* Database connections */
|
|
static PGcancel *volatile cancel_conn = NULL;
|
|
|
|
/* Interrupted by SIGINT (Ctrl+C) ? */
|
|
bool interrupted = false;
|
|
static bool in_cleanup = false;
|
|
|
|
static bool parse_pair(const char buffer[], char key[], char value[]);
|
|
|
|
/* Connection routines */
|
|
static void init_cancel_handler(void);
|
|
static void on_before_exec(PGconn *conn);
|
|
static void on_after_exec(void);
|
|
static void on_interrupt(void);
|
|
static void on_cleanup(void);
|
|
static void exit_or_abort(int exitcode);
|
|
static const char *get_username(void);
|
|
|
|
/*
|
|
* Unit conversion tables.
|
|
*
|
|
* Copied from guc.c.
|
|
*/
|
|
#define MAX_UNIT_LEN 3 /* length of longest recognized unit string */
|
|
|
|
typedef struct
|
|
{
|
|
char unit[MAX_UNIT_LEN + 1]; /* unit, as a string, like "kB" or
|
|
* "min" */
|
|
int base_unit; /* OPTION_UNIT_XXX */
|
|
int multiplier; /* If positive, multiply the value with this
|
|
* for unit -> base_unit conversion. If
|
|
* negative, divide (with the absolute value) */
|
|
} unit_conversion;
|
|
|
|
static const char *memory_units_hint = "Valid units for this parameter are \"kB\", \"MB\", \"GB\", and \"TB\".";
|
|
|
|
static const unit_conversion memory_unit_conversion_table[] =
|
|
{
|
|
{"TB", OPTION_UNIT_KB, 1024 * 1024 * 1024},
|
|
{"GB", OPTION_UNIT_KB, 1024 * 1024},
|
|
{"MB", OPTION_UNIT_KB, 1024},
|
|
{"kB", OPTION_UNIT_KB, 1},
|
|
|
|
{"TB", OPTION_UNIT_BLOCKS, (1024 * 1024 * 1024) / (BLCKSZ / 1024)},
|
|
{"GB", OPTION_UNIT_BLOCKS, (1024 * 1024) / (BLCKSZ / 1024)},
|
|
{"MB", OPTION_UNIT_BLOCKS, 1024 / (BLCKSZ / 1024)},
|
|
{"kB", OPTION_UNIT_BLOCKS, -(BLCKSZ / 1024)},
|
|
|
|
{"TB", OPTION_UNIT_XBLOCKS, (1024 * 1024 * 1024) / (XLOG_BLCKSZ / 1024)},
|
|
{"GB", OPTION_UNIT_XBLOCKS, (1024 * 1024) / (XLOG_BLCKSZ / 1024)},
|
|
{"MB", OPTION_UNIT_XBLOCKS, 1024 / (XLOG_BLCKSZ / 1024)},
|
|
{"kB", OPTION_UNIT_XBLOCKS, -(XLOG_BLCKSZ / 1024)},
|
|
|
|
{"TB", OPTION_UNIT_XSEGS, (1024 * 1024 * 1024) / (XLOG_SEG_SIZE / 1024)},
|
|
{"GB", OPTION_UNIT_XSEGS, (1024 * 1024) / (XLOG_SEG_SIZE / 1024)},
|
|
{"MB", OPTION_UNIT_XSEGS, -(XLOG_SEG_SIZE / (1024 * 1024))},
|
|
{"kB", OPTION_UNIT_XSEGS, -(XLOG_SEG_SIZE / 1024)},
|
|
|
|
{""} /* end of table marker */
|
|
};
|
|
|
|
static const char *time_units_hint = "Valid units for this parameter are \"ms\", \"s\", \"min\", \"h\", and \"d\".";
|
|
|
|
static const unit_conversion time_unit_conversion_table[] =
|
|
{
|
|
{"d", OPTION_UNIT_MS, 1000 * 60 * 60 * 24},
|
|
{"h", OPTION_UNIT_MS, 1000 * 60 * 60},
|
|
{"min", OPTION_UNIT_MS, 1000 * 60},
|
|
{"s", OPTION_UNIT_MS, 1000},
|
|
{"ms", OPTION_UNIT_MS, 1},
|
|
|
|
{"d", OPTION_UNIT_S, 60 * 60 * 24},
|
|
{"h", OPTION_UNIT_S, 60 * 60},
|
|
{"min", OPTION_UNIT_S, 60},
|
|
{"s", OPTION_UNIT_S, 1},
|
|
{"ms", OPTION_UNIT_S, -1000},
|
|
|
|
{"d", OPTION_UNIT_MIN, 60 * 24},
|
|
{"h", OPTION_UNIT_MIN, 60},
|
|
{"min", OPTION_UNIT_MIN, 1},
|
|
{"s", OPTION_UNIT_MIN, -60},
|
|
{"ms", OPTION_UNIT_MIN, -1000 * 60},
|
|
|
|
{""} /* end of table marker */
|
|
};
|
|
|
|
static size_t
|
|
option_length(const pgut_option opts[])
|
|
{
|
|
size_t len;
|
|
for (len = 0; opts && opts[len].type; len++) { }
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
option_has_arg(char type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case 'b':
|
|
case 'B':
|
|
return no_argument;
|
|
default:
|
|
return required_argument;
|
|
}
|
|
}
|
|
|
|
static void
|
|
option_copy(struct option dst[], const pgut_option opts[], size_t len)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
dst[i].name = opts[i].lname;
|
|
dst[i].has_arg = option_has_arg(opts[i].type);
|
|
dst[i].flag = NULL;
|
|
dst[i].val = opts[i].sname;
|
|
}
|
|
}
|
|
|
|
static pgut_option *
|
|
option_find(int c, pgut_option opts1[])
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; opts1 && opts1[i].type; i++)
|
|
if (opts1[i].sname == c)
|
|
return &opts1[i];
|
|
|
|
return NULL; /* not found */
|
|
}
|
|
|
|
static void
|
|
assign_option(pgut_option *opt, const char *optarg, pgut_optsrc src)
|
|
{
|
|
const char *message;
|
|
|
|
if (opt == NULL)
|
|
{
|
|
fprintf(stderr, "Try \"%s --help\" for more information.\n", PROGRAM_NAME);
|
|
exit_or_abort(ERROR);
|
|
}
|
|
|
|
if (opt->source > src)
|
|
{
|
|
/* high prior value has been set already. */
|
|
return;
|
|
}
|
|
/* Allow duplicate entries for function option */
|
|
else if (src >= SOURCE_CMDLINE && opt->source >= src && opt->type != 'f')
|
|
{
|
|
message = "specified only once";
|
|
}
|
|
else
|
|
{
|
|
/* can be overwritten if non-command line source */
|
|
opt->source = src;
|
|
|
|
switch (opt->type)
|
|
{
|
|
case 'b':
|
|
case 'B':
|
|
if (optarg == NULL)
|
|
{
|
|
*((bool *) opt->var) = (opt->type == 'b');
|
|
return;
|
|
}
|
|
else if (parse_bool(optarg, (bool *) opt->var))
|
|
{
|
|
return;
|
|
}
|
|
message = "a boolean";
|
|
break;
|
|
case 'f':
|
|
((pgut_optfn) opt->var)(opt, optarg);
|
|
return;
|
|
case 'i':
|
|
if (parse_int32(optarg, opt->var))
|
|
return;
|
|
message = "a 32bit signed integer";
|
|
break;
|
|
case 'u':
|
|
if (parse_uint32(optarg, opt->var))
|
|
return;
|
|
message = "a 32bit unsigned integer";
|
|
break;
|
|
case 'I':
|
|
if (parse_int64(optarg, opt->var))
|
|
return;
|
|
message = "a 64bit signed integer";
|
|
break;
|
|
case 'U':
|
|
if (parse_uint64(optarg, opt->var))
|
|
return;
|
|
message = "a 64bit unsigned integer";
|
|
break;
|
|
case 's':
|
|
if (opt->source != SOURCE_DEFAULT)
|
|
free(*(char **) opt->var);
|
|
*(char **) opt->var = pgut_strdup(optarg);
|
|
return;
|
|
case 't':
|
|
if (parse_time(optarg, opt->var))
|
|
return;
|
|
message = "a time";
|
|
break;
|
|
default:
|
|
elog(ERROR, "invalid option type: %c", opt->type);
|
|
return; /* keep compiler quiet */
|
|
}
|
|
}
|
|
|
|
if (isprint(opt->sname))
|
|
elog(ERROR, "option -%c, --%s should be %s: '%s'",
|
|
opt->sname, opt->lname, message, optarg);
|
|
else
|
|
elog(ERROR, "option --%s should be %s: '%s'",
|
|
opt->lname, message, optarg);
|
|
}
|
|
|
|
/*
|
|
* Try to interpret value as boolean value. Valid values are: true,
|
|
* false, yes, no, on, off, 1, 0; as well as unique prefixes thereof.
|
|
* If the string parses okay, return true, else false.
|
|
* If okay and result is not NULL, return the value in *result.
|
|
*/
|
|
bool
|
|
parse_bool(const char *value, bool *result)
|
|
{
|
|
return parse_bool_with_len(value, strlen(value), result);
|
|
}
|
|
|
|
bool
|
|
parse_bool_with_len(const char *value, size_t len, bool *result)
|
|
{
|
|
switch (*value)
|
|
{
|
|
case 't':
|
|
case 'T':
|
|
if (pg_strncasecmp(value, "true", len) == 0)
|
|
{
|
|
if (result)
|
|
*result = true;
|
|
return true;
|
|
}
|
|
break;
|
|
case 'f':
|
|
case 'F':
|
|
if (pg_strncasecmp(value, "false", len) == 0)
|
|
{
|
|
if (result)
|
|
*result = false;
|
|
return true;
|
|
}
|
|
break;
|
|
case 'y':
|
|
case 'Y':
|
|
if (pg_strncasecmp(value, "yes", len) == 0)
|
|
{
|
|
if (result)
|
|
*result = true;
|
|
return true;
|
|
}
|
|
break;
|
|
case 'n':
|
|
case 'N':
|
|
if (pg_strncasecmp(value, "no", len) == 0)
|
|
{
|
|
if (result)
|
|
*result = false;
|
|
return true;
|
|
}
|
|
break;
|
|
case 'o':
|
|
case 'O':
|
|
/* 'o' is not unique enough */
|
|
if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
|
|
{
|
|
if (result)
|
|
*result = true;
|
|
return true;
|
|
}
|
|
else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)
|
|
{
|
|
if (result)
|
|
*result = false;
|
|
return true;
|
|
}
|
|
break;
|
|
case '1':
|
|
if (len == 1)
|
|
{
|
|
if (result)
|
|
*result = true;
|
|
return true;
|
|
}
|
|
break;
|
|
case '0':
|
|
if (len == 1)
|
|
{
|
|
if (result)
|
|
*result = false;
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (result)
|
|
*result = false; /* suppress compiler warning */
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Parse string as 32bit signed int.
|
|
* valid range: -2147483648 ~ 2147483647
|
|
*/
|
|
bool
|
|
parse_int32(const char *value, int32 *result)
|
|
{
|
|
int64 val;
|
|
char *endptr;
|
|
|
|
if (strcmp(value, INFINITE_STR) == 0)
|
|
{
|
|
*result = INT_MAX;
|
|
return true;
|
|
}
|
|
|
|
errno = 0;
|
|
val = strtol(value, &endptr, 0);
|
|
if (endptr == value || *endptr)
|
|
return false;
|
|
|
|
if (errno == ERANGE || val != (int64) ((int32) val))
|
|
return false;
|
|
|
|
*result = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Parse string as 32bit unsigned int.
|
|
* valid range: 0 ~ 4294967295 (2^32-1)
|
|
*/
|
|
bool
|
|
parse_uint32(const char *value, uint32 *result)
|
|
{
|
|
uint64 val;
|
|
char *endptr;
|
|
|
|
if (strcmp(value, INFINITE_STR) == 0)
|
|
{
|
|
*result = UINT_MAX;
|
|
return true;
|
|
}
|
|
|
|
errno = 0;
|
|
val = strtoul(value, &endptr, 0);
|
|
if (endptr == value || *endptr)
|
|
return false;
|
|
|
|
if (errno == ERANGE || val != (uint64) ((uint32) val))
|
|
return false;
|
|
|
|
*result = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Parse string as int64
|
|
* valid range: -9223372036854775808 ~ 9223372036854775807
|
|
*/
|
|
bool
|
|
parse_int64(const char *value, int64 *result)
|
|
{
|
|
int64 val;
|
|
char *endptr;
|
|
|
|
if (strcmp(value, INFINITE_STR) == 0)
|
|
{
|
|
*result = LLONG_MAX;
|
|
return true;
|
|
}
|
|
|
|
errno = 0;
|
|
#if defined(HAVE_LONG_INT_64)
|
|
val = strtol(value, &endptr, 0);
|
|
#elif defined(HAVE_LONG_LONG_INT_64)
|
|
val = strtoll(value, &endptr, 0);
|
|
#else
|
|
val = strtol(value, &endptr, 0);
|
|
#endif
|
|
if (endptr == value || *endptr)
|
|
return false;
|
|
|
|
if (errno == ERANGE)
|
|
return false;
|
|
|
|
*result = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Parse string as uint64
|
|
* valid range: 0 ~ (2^64-1)
|
|
*/
|
|
bool
|
|
parse_uint64(const char *value, uint64 *result)
|
|
{
|
|
uint64 val;
|
|
char *endptr;
|
|
|
|
if (strcmp(value, INFINITE_STR) == 0)
|
|
{
|
|
#if defined(HAVE_LONG_INT_64)
|
|
*result = ULONG_MAX;
|
|
#elif defined(HAVE_LONG_LONG_INT_64)
|
|
*result = ULLONG_MAX;
|
|
#else
|
|
*result = ULONG_MAX;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
errno = 0;
|
|
#if defined(HAVE_LONG_INT_64)
|
|
val = strtoul(value, &endptr, 0);
|
|
#elif defined(HAVE_LONG_LONG_INT_64)
|
|
val = strtoull(value, &endptr, 0);
|
|
#else
|
|
val = strtoul(value, &endptr, 0);
|
|
#endif
|
|
if (endptr == value || *endptr)
|
|
return false;
|
|
|
|
if (errno == ERANGE)
|
|
return false;
|
|
|
|
*result = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Convert ISO-8601 format string to time_t value.
|
|
*/
|
|
bool
|
|
parse_time(const char *value, time_t *time)
|
|
{
|
|
size_t len;
|
|
char *tmp;
|
|
int i;
|
|
struct tm tm;
|
|
char junk[2];
|
|
|
|
/* tmp = replace( value, !isalnum, ' ' ) */
|
|
tmp = pgut_malloc(strlen(value) + + 1);
|
|
len = 0;
|
|
for (i = 0; value[i]; i++)
|
|
tmp[len++] = (IsAlnum(value[i]) ? value[i] : ' ');
|
|
tmp[len] = '\0';
|
|
|
|
/* parse for "YYYY-MM-DD HH:MI:SS" */
|
|
memset(&tm, 0, sizeof(tm));
|
|
tm.tm_year = 0; /* tm_year is year - 1900 */
|
|
tm.tm_mon = 0; /* tm_mon is 0 - 11 */
|
|
tm.tm_mday = 1; /* tm_mday is 1 - 31 */
|
|
tm.tm_hour = 0;
|
|
tm.tm_min = 0;
|
|
tm.tm_sec = 0;
|
|
i = sscanf(tmp, "%04d %02d %02d %02d %02d %02d%1s",
|
|
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
|
|
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, junk);
|
|
free(tmp);
|
|
|
|
if (i < 1 || 6 < i)
|
|
return false;
|
|
|
|
/* adjust year */
|
|
if (tm.tm_year < 100)
|
|
tm.tm_year += 2000 - 1900;
|
|
else if (tm.tm_year >= 1900)
|
|
tm.tm_year -= 1900;
|
|
|
|
/* adjust month */
|
|
if (i > 1)
|
|
tm.tm_mon -= 1;
|
|
|
|
/* determine whether Daylight Saving Time is in effect */
|
|
tm.tm_isdst = -1;
|
|
|
|
*time = mktime(&tm);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
|
|
* to the given base unit. 'value' and 'unit' are the input value and unit
|
|
* to convert from. The converted value is stored in *base_value.
|
|
*
|
|
* Returns true on success, false if the input unit is not recognized.
|
|
*/
|
|
static bool
|
|
convert_to_base_unit(int64 value, const char *unit,
|
|
int base_unit, int64 *base_value)
|
|
{
|
|
const unit_conversion *table;
|
|
int i;
|
|
|
|
if (base_unit & OPTION_UNIT_MEMORY)
|
|
table = memory_unit_conversion_table;
|
|
else
|
|
table = time_unit_conversion_table;
|
|
|
|
for (i = 0; *table[i].unit; i++)
|
|
{
|
|
if (base_unit == table[i].base_unit &&
|
|
strcmp(unit, table[i].unit) == 0)
|
|
{
|
|
if (table[i].multiplier < 0)
|
|
*base_value = value / (-table[i].multiplier);
|
|
else
|
|
*base_value = value * table[i].multiplier;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Try to parse value as an integer. The accepted formats are the
|
|
* usual decimal, octal, or hexadecimal formats, optionally followed by
|
|
* a unit name if "flags" indicates a unit is allowed.
|
|
*
|
|
* If the string parses okay, return true, else false.
|
|
* If okay and result is not NULL, return the value in *result.
|
|
* If not okay and hintmsg is not NULL, *hintmsg is set to a suitable
|
|
* HINT message, or NULL if no hint provided.
|
|
*/
|
|
bool
|
|
parse_int(const char *value, int *result, int flags, const char **hintmsg)
|
|
{
|
|
int64 val;
|
|
char *endptr;
|
|
|
|
/* To suppress compiler warnings, always set output params */
|
|
if (result)
|
|
*result = 0;
|
|
if (hintmsg)
|
|
*hintmsg = NULL;
|
|
|
|
/* We assume here that int64 is at least as wide as long */
|
|
errno = 0;
|
|
val = strtol(value, &endptr, 0);
|
|
|
|
if (endptr == value)
|
|
return false; /* no HINT for integer syntax error */
|
|
|
|
if (errno == ERANGE || val != (int64) ((int32) val))
|
|
{
|
|
if (hintmsg)
|
|
*hintmsg = "Value exceeds integer range.";
|
|
return false;
|
|
}
|
|
|
|
/* allow whitespace between integer and unit */
|
|
while (isspace((unsigned char) *endptr))
|
|
endptr++;
|
|
|
|
/* Handle possible unit */
|
|
if (*endptr != '\0')
|
|
{
|
|
char unit[MAX_UNIT_LEN + 1];
|
|
int unitlen;
|
|
bool converted = false;
|
|
|
|
if ((flags & OPTION_UNIT) == 0)
|
|
return false; /* this setting does not accept a unit */
|
|
|
|
unitlen = 0;
|
|
while (*endptr != '\0' && !isspace((unsigned char) *endptr) &&
|
|
unitlen < MAX_UNIT_LEN)
|
|
unit[unitlen++] = *(endptr++);
|
|
unit[unitlen] = '\0';
|
|
/* allow whitespace after unit */
|
|
while (isspace((unsigned char) *endptr))
|
|
endptr++;
|
|
|
|
if (*endptr == '\0')
|
|
converted = convert_to_base_unit(val, unit, (flags & OPTION_UNIT),
|
|
&val);
|
|
if (!converted)
|
|
{
|
|
/* invalid unit, or garbage after the unit; set hint and fail. */
|
|
if (hintmsg)
|
|
{
|
|
if (flags & OPTION_UNIT_MEMORY)
|
|
*hintmsg = memory_units_hint;
|
|
else
|
|
*hintmsg = time_units_hint;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Check for overflow due to units conversion */
|
|
if (val != (int64) ((int32) val))
|
|
{
|
|
if (hintmsg)
|
|
*hintmsg = "Value exceeds integer range.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (result)
|
|
*result = (int) val;
|
|
return true;
|
|
}
|
|
|
|
static char *
|
|
longopts_to_optstring(const struct option opts[])
|
|
{
|
|
size_t len;
|
|
char *result;
|
|
char *s;
|
|
|
|
for (len = 0; opts[len].name; len++) { }
|
|
result = pgut_malloc(len * 2 + 1);
|
|
|
|
s = result;
|
|
for (len = 0; opts[len].name; len++)
|
|
{
|
|
if (!isprint(opts[len].val))
|
|
continue;
|
|
*s++ = opts[len].val;
|
|
if (opts[len].has_arg != no_argument)
|
|
*s++ = ':';
|
|
}
|
|
*s = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
pgut_getopt_env(pgut_option options[])
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; options && options[i].type; i++)
|
|
{
|
|
pgut_option *opt = &options[i];
|
|
const char *value = NULL;
|
|
|
|
/* If option was already set do not check env */
|
|
if (opt->source > SOURCE_ENV || opt->allowed < SOURCE_ENV)
|
|
continue;
|
|
|
|
if (strcmp(opt->lname, "pgdata") == 0)
|
|
value = getenv("PGDATA");
|
|
if (strcmp(opt->lname, "port") == 0)
|
|
value = getenv("PGPORT");
|
|
if (strcmp(opt->lname, "host") == 0)
|
|
value = getenv("PGHOST");
|
|
if (strcmp(opt->lname, "username") == 0)
|
|
value = getenv("PGUSER");
|
|
if (strcmp(opt->lname, "pgdatabase") == 0)
|
|
{
|
|
value = getenv("PGDATABASE");
|
|
if (value == NULL)
|
|
value = getenv("PGUSER");
|
|
if (value == NULL)
|
|
value = get_username();
|
|
}
|
|
|
|
if (value)
|
|
assign_option(opt, value, SOURCE_ENV);
|
|
}
|
|
}
|
|
|
|
int
|
|
pgut_getopt(int argc, char **argv, pgut_option options[])
|
|
{
|
|
int c;
|
|
int optindex = 0;
|
|
char *optstring;
|
|
pgut_option *opt;
|
|
struct option *longopts;
|
|
size_t len1;
|
|
|
|
len1 = option_length(options);
|
|
longopts = pgut_newarray(struct option, len1 + 1);
|
|
option_copy(longopts, options, len1);
|
|
|
|
optstring = longopts_to_optstring(longopts);
|
|
|
|
/* Assign named options */
|
|
while ((c = getopt_long(argc, argv, optstring, longopts, &optindex)) != -1)
|
|
{
|
|
opt = option_find(c, options);
|
|
if (opt && opt->allowed < SOURCE_CMDLINE)
|
|
elog(ERROR, "option %s cannot be specified in command line",
|
|
opt->lname);
|
|
/* Check 'opt == NULL' is performed in assign_option() */
|
|
assign_option(opt, optarg, SOURCE_CMDLINE);
|
|
}
|
|
|
|
init_cancel_handler();
|
|
atexit(on_cleanup);
|
|
|
|
return optind;
|
|
}
|
|
|
|
/* compare two strings ignore cases and ignore -_ */
|
|
static bool
|
|
key_equals(const char *lhs, const char *rhs)
|
|
{
|
|
for (; *lhs && *rhs; lhs++, rhs++)
|
|
{
|
|
if (strchr("-_ ", *lhs))
|
|
{
|
|
if (!strchr("-_ ", *rhs))
|
|
return false;
|
|
}
|
|
else if (ToLower(*lhs) != ToLower(*rhs))
|
|
return false;
|
|
}
|
|
|
|
return *lhs == '\0' && *rhs == '\0';
|
|
}
|
|
|
|
/*
|
|
* Get configuration from configuration file.
|
|
*/
|
|
void
|
|
pgut_readopt(const char *path, pgut_option options[], int elevel)
|
|
{
|
|
FILE *fp;
|
|
char buf[1024];
|
|
char key[1024];
|
|
char value[1024];
|
|
|
|
if (!options)
|
|
return;
|
|
|
|
if ((fp = pgut_fopen(path, "rt", true)) == NULL)
|
|
return;
|
|
|
|
while (fgets(buf, lengthof(buf), fp))
|
|
{
|
|
size_t i;
|
|
|
|
for (i = strlen(buf); i > 0 && IsSpace(buf[i - 1]); i--)
|
|
buf[i - 1] = '\0';
|
|
|
|
if (parse_pair(buf, key, value))
|
|
{
|
|
for (i = 0; options[i].type; i++)
|
|
{
|
|
pgut_option *opt = &options[i];
|
|
|
|
if (key_equals(key, opt->lname))
|
|
{
|
|
if (opt->allowed < SOURCE_FILE &&
|
|
opt->allowed != SOURCE_FILE_STRICT)
|
|
elog(elevel, "option %s cannot specified in file", opt->lname);
|
|
else if (opt->source <= SOURCE_FILE)
|
|
assign_option(opt, value, SOURCE_FILE);
|
|
break;
|
|
}
|
|
}
|
|
if (!options[i].type)
|
|
elog(elevel, "invalid option \"%s\"", key);
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
static const char *
|
|
skip_space(const char *str, const char *line)
|
|
{
|
|
while (IsSpace(*str)) { str++; }
|
|
return str;
|
|
}
|
|
|
|
static const char *
|
|
get_next_token(const char *src, char *dst, const char *line)
|
|
{
|
|
const char *s;
|
|
int i;
|
|
int j;
|
|
|
|
if ((s = skip_space(src, line)) == NULL)
|
|
return NULL;
|
|
|
|
/* parse quoted string */
|
|
if (*s == '\'')
|
|
{
|
|
s++;
|
|
for (i = 0, j = 0; s[i] != '\0'; i++)
|
|
{
|
|
if (s[i] == '\\')
|
|
{
|
|
i++;
|
|
switch (s[i])
|
|
{
|
|
case 'b':
|
|
dst[j] = '\b';
|
|
break;
|
|
case 'f':
|
|
dst[j] = '\f';
|
|
break;
|
|
case 'n':
|
|
dst[j] = '\n';
|
|
break;
|
|
case 'r':
|
|
dst[j] = '\r';
|
|
break;
|
|
case 't':
|
|
dst[j] = '\t';
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
{
|
|
int k;
|
|
long octVal = 0;
|
|
|
|
for (k = 0;
|
|
s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
|
|
k++)
|
|
octVal = (octVal << 3) + (s[i + k] - '0');
|
|
i += k - 1;
|
|
dst[j] = ((char) octVal);
|
|
}
|
|
break;
|
|
default:
|
|
dst[j] = s[i];
|
|
break;
|
|
}
|
|
}
|
|
else if (s[i] == '\'')
|
|
{
|
|
i++;
|
|
/* doubled quote becomes just one quote */
|
|
if (s[i] == '\'')
|
|
dst[j] = s[i];
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
dst[j] = s[i];
|
|
j++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = j = strcspn(s, "# \n\r\t\v");
|
|
memcpy(dst, s, j);
|
|
}
|
|
|
|
dst[j] = '\0';
|
|
return s + i;
|
|
}
|
|
|
|
static bool
|
|
parse_pair(const char buffer[], char key[], char value[])
|
|
{
|
|
const char *start;
|
|
const char *end;
|
|
|
|
key[0] = value[0] = '\0';
|
|
|
|
/*
|
|
* parse key
|
|
*/
|
|
start = buffer;
|
|
if ((start = skip_space(start, buffer)) == NULL)
|
|
return false;
|
|
|
|
end = start + strcspn(start, "=# \n\r\t\v");
|
|
|
|
/* skip blank buffer */
|
|
if (end - start <= 0)
|
|
{
|
|
if (*start == '=')
|
|
elog(ERROR, "syntax error in \"%s\"", buffer);
|
|
return false;
|
|
}
|
|
|
|
/* key found */
|
|
strncpy(key, start, end - start);
|
|
key[end - start] = '\0';
|
|
|
|
/* find key and value split char */
|
|
if ((start = skip_space(end, buffer)) == NULL)
|
|
return false;
|
|
|
|
if (*start != '=')
|
|
{
|
|
elog(ERROR, "syntax error in \"%s\"", buffer);
|
|
return false;
|
|
}
|
|
|
|
start++;
|
|
|
|
/*
|
|
* parse value
|
|
*/
|
|
if ((end = get_next_token(start, value, buffer)) == NULL)
|
|
return false;
|
|
|
|
if ((start = skip_space(end, buffer)) == NULL)
|
|
return false;
|
|
|
|
if (*start != '\0' && *start != '#')
|
|
{
|
|
elog(ERROR, "syntax error in \"%s\"", buffer);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
if (password)
|
|
{
|
|
free(password);
|
|
password = NULL;
|
|
}
|
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
if (username == NULL)
|
|
simple_prompt("Password: ", password, 100, false);
|
|
else
|
|
{
|
|
char message[256];
|
|
snprintf(message, lengthof(message), "Password for user %s: ", username);
|
|
simple_prompt(message, password, 100, false);
|
|
}
|
|
#else
|
|
if (username == NULL)
|
|
password = simple_prompt("Password: ", 100, false);
|
|
else
|
|
{
|
|
char message[256];
|
|
snprintf(message, lengthof(message), "Password for user %s: ", username);
|
|
password = simple_prompt(message, 100, false);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
PGconn *
|
|
pgut_connect(const char *dbname)
|
|
{
|
|
PGconn *conn;
|
|
if (interrupted && !in_cleanup)
|
|
elog(ERROR, "interrupted");
|
|
|
|
/* Start the connection. Loop until we have a password if requested by backend. */
|
|
for (;;)
|
|
{
|
|
conn = PQsetdbLogin(host, port, NULL, NULL, dbname, username, password);
|
|
|
|
if (PQstatus(conn) == CONNECTION_OK)
|
|
return conn;
|
|
|
|
if (conn && PQconnectionNeedsPassword(conn) && prompt_password)
|
|
{
|
|
PQfinish(conn);
|
|
prompt_for_password(username);
|
|
continue;
|
|
}
|
|
elog(ERROR, "could not connect to database %s: %s",
|
|
dbname, PQerrorMessage(conn));
|
|
|
|
PQfinish(conn);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
pgut_disconnect(PGconn *conn)
|
|
{
|
|
if (conn)
|
|
PQfinish(conn);
|
|
}
|
|
|
|
/* set/get host and port for connecting standby server */
|
|
const char *
|
|
pgut_get_host()
|
|
{
|
|
return host;
|
|
}
|
|
|
|
const char *
|
|
pgut_get_port()
|
|
{
|
|
return port;
|
|
}
|
|
|
|
void
|
|
pgut_set_host(const char *new_host)
|
|
{
|
|
host = new_host;
|
|
}
|
|
|
|
void
|
|
pgut_set_port(const char *new_port)
|
|
{
|
|
port = new_port;
|
|
}
|
|
|
|
PGresult *
|
|
pgut_execute(PGconn* conn, const char *query, int nParams, const char **params)
|
|
{
|
|
PGresult *res;
|
|
|
|
if (interrupted && !in_cleanup)
|
|
elog(ERROR, "interrupted");
|
|
|
|
/* write query to elog if verbose */
|
|
if (log_level <= LOG)
|
|
{
|
|
int i;
|
|
|
|
if (strchr(query, '\n'))
|
|
elog(LOG, "(query)\n%s", query);
|
|
else
|
|
elog(LOG, "(query) %s", query);
|
|
for (i = 0; i < nParams; i++)
|
|
elog(LOG, "\t(param:%d) = %s", i, params[i] ? params[i] : "(null)");
|
|
}
|
|
|
|
if (conn == NULL)
|
|
{
|
|
elog(ERROR, "not connected");
|
|
return NULL;
|
|
}
|
|
|
|
on_before_exec(conn);
|
|
if (nParams == 0)
|
|
res = PQexec(conn, query);
|
|
else
|
|
res = PQexecParams(conn, query, nParams, NULL, params, NULL, NULL, 0);
|
|
on_after_exec();
|
|
|
|
switch (PQresultStatus(res))
|
|
{
|
|
case PGRES_TUPLES_OK:
|
|
case PGRES_COMMAND_OK:
|
|
case PGRES_COPY_IN:
|
|
break;
|
|
default:
|
|
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");
|
|
|
|
/* write query to elog if verbose */
|
|
if (log_level <= LOG)
|
|
{
|
|
int i;
|
|
|
|
if (strchr(query, '\n'))
|
|
elog(LOG, "(query)\n%s", query);
|
|
else
|
|
elog(LOG, "(query) %s", query);
|
|
for (i = 0; i < nParams; i++)
|
|
elog(LOG, "\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;
|
|
}
|
|
|
|
int
|
|
pgut_wait(int num, PGconn *connections[], struct timeval *timeout)
|
|
{
|
|
/* all connections are busy. wait for finish */
|
|
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; /* timeout */
|
|
|
|
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 *old;
|
|
|
|
if (in_cleanup)
|
|
return; /* forbid cancel during cleanup */
|
|
|
|
#ifdef WIN32
|
|
EnterCriticalSection(&cancelConnLock);
|
|
#endif
|
|
|
|
/* Free the old one if we have one */
|
|
old = cancel_conn;
|
|
|
|
/* be sure handle_sigint doesn't use pointer while freeing */
|
|
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(void)
|
|
{
|
|
PGcancel *old;
|
|
|
|
if (in_cleanup)
|
|
return; /* forbid cancel during cleanup */
|
|
|
|
#ifdef WIN32
|
|
EnterCriticalSection(&cancelConnLock);
|
|
#endif
|
|
|
|
old = cancel_conn;
|
|
|
|
/* be sure handle_sigint doesn't use pointer while freeing */
|
|
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];
|
|
|
|
/* Set interruped flag */
|
|
interrupted = true;
|
|
|
|
/* Send QueryCancel if we are processing a database query */
|
|
if (!in_cleanup && cancel_conn != NULL &&
|
|
PQcancel(cancel_conn, errbuf, sizeof(errbuf)))
|
|
{
|
|
elog(WARNING, "Cancel request sent");
|
|
}
|
|
|
|
errno = save_errno; /* just in case the write changed it */
|
|
}
|
|
|
|
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_atexit_push(pgut_atexit_callback callback, void *userdata)
|
|
{
|
|
pgut_atexit_item *item;
|
|
|
|
AssertArg(callback != NULL);
|
|
|
|
item = pgut_new(pgut_atexit_item);
|
|
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;
|
|
|
|
for (item = pgut_atexit_stack; item; item = item->next)
|
|
item->callback(fatal, item->userdata);
|
|
}
|
|
|
|
static void
|
|
on_cleanup(void)
|
|
{
|
|
in_cleanup = true;
|
|
interrupted = false;
|
|
call_atexit_callbacks(false);
|
|
}
|
|
|
|
static void
|
|
exit_or_abort(int exitcode)
|
|
{
|
|
if (in_cleanup)
|
|
{
|
|
/* oops, error in cleanup*/
|
|
call_atexit_callbacks(true);
|
|
abort();
|
|
}
|
|
else
|
|
{
|
|
/* normal exit */
|
|
exit(exitcode);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns the current user name.
|
|
*/
|
|
static const char *
|
|
get_username(void)
|
|
{
|
|
const char *ret;
|
|
|
|
#ifndef WIN32
|
|
struct passwd *pw;
|
|
|
|
pw = getpwuid(geteuid());
|
|
ret = (pw ? pw->pw_name : NULL);
|
|
#else
|
|
static char username[128]; /* remains after function exit */
|
|
DWORD len = sizeof(username) - 1;
|
|
|
|
if (GetUserName(username, &len))
|
|
ret = username;
|
|
else
|
|
{
|
|
_dosmaperr(GetLastError());
|
|
ret = NULL;
|
|
}
|
|
#endif
|
|
|
|
if (ret == NULL)
|
|
elog(ERROR, "%s: could not get current user name: %s",
|
|
PROGRAM_NAME, strerror(errno));
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
appendStringInfoFile(StringInfo str, FILE *fp)
|
|
{
|
|
AssertArg(str != NULL);
|
|
AssertArg(fp != NULL);
|
|
|
|
for (;;)
|
|
{
|
|
int rc;
|
|
|
|
if (str->maxlen - str->len < 2 && enlargeStringInfo(str, 1024) == 0)
|
|
return errno = ENOMEM;
|
|
|
|
rc = fread(str->data + str->len, 1, str->maxlen - str->len - 1, fp);
|
|
if (rc == 0)
|
|
break;
|
|
else if (rc > 0)
|
|
{
|
|
str->len += rc;
|
|
str->data[str->len] = '\0';
|
|
}
|
|
else if (ferror(fp) && errno != EINTR)
|
|
return errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
appendStringInfoFd(StringInfo str, int fd)
|
|
{
|
|
AssertArg(str != NULL);
|
|
AssertArg(fd != -1);
|
|
|
|
for (;;)
|
|
{
|
|
int rc;
|
|
|
|
if (str->maxlen - str->len < 2 && enlargeStringInfo(str, 1024) == 0)
|
|
return errno = ENOMEM;
|
|
|
|
rc = read(fd, str->data + str->len, str->maxlen - str->len - 1);
|
|
if (rc == 0)
|
|
break;
|
|
else if (rc > 0)
|
|
{
|
|
str->len += rc;
|
|
str->data[str->len] = '\0';
|
|
}
|
|
else if (errno != EINTR)
|
|
return errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void *
|
|
pgut_malloc(size_t size)
|
|
{
|
|
char *ret;
|
|
|
|
if ((ret = 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 size)
|
|
{
|
|
char *ret;
|
|
|
|
if ((ret = realloc(p, size)) == NULL)
|
|
elog(ERROR, "could not re-allocate memory (%lu bytes): %s",
|
|
(unsigned long) size, strerror(errno));
|
|
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;
|
|
}
|
|
|
|
char *
|
|
strdup_with_len(const char *str, size_t len)
|
|
{
|
|
char *r;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
r = pgut_malloc(len + 1);
|
|
memcpy(r, str, len);
|
|
r[len] = '\0';
|
|
return r;
|
|
}
|
|
|
|
/* strdup but trim whitespaces at head and tail */
|
|
char *
|
|
strdup_trim(const char *str)
|
|
{
|
|
size_t len;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
while (IsSpace(str[0])) { str++; }
|
|
len = strlen(str);
|
|
while (len > 0 && IsSpace(str[len - 1])) { len--; }
|
|
|
|
return strdup_with_len(str, len);
|
|
}
|
|
|
|
FILE *
|
|
pgut_fopen(const char *path, const char *mode, bool missing_ok)
|
|
{
|
|
FILE *fp;
|
|
|
|
if ((fp = fopen(path, mode)) == 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_sigint(SIGNAL_ARGS)
|
|
{
|
|
on_interrupt();
|
|
}
|
|
|
|
static void
|
|
init_cancel_handler(void)
|
|
{
|
|
pqsignal(SIGINT, handle_sigint);
|
|
}
|
|
#else /* WIN32 */
|
|
|
|
/*
|
|
* 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 for any signals not being handled */
|
|
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); /* rounded up */
|
|
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; /* infinite */
|
|
}
|
|
|
|
/* sleep only one second because Ctrl+C doesn't interrupt select. */
|
|
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)
|
|
{
|
|
/* succeeded or error */
|
|
return ret;
|
|
}
|
|
else if (interrupted)
|
|
{
|
|
errno = EINTR;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0; /* timeout */
|
|
}
|
|
|
|
#endif /* WIN32 */
|