pg_repack/bin/pgut/pgut-fe.c
Takahiro Itagaki 960930b645 Fixed database corruption when target tables have dropped columns, and
there are views or functions depending on columns after dropped ones.
The issue was reported by depesz, and original patch by Denish Patel.

Improved documentation how to build binaries from source.

COPYRIGHT updated.
2011-04-29 05:06:48 +00:00

693 lines
14 KiB
C
Executable File

/*-------------------------------------------------------------------------
* pgut-fe.c
*
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2011, Itagaki Takahiro
*-------------------------------------------------------------------------
*/
#define FRONTEND
#include "pgut-fe.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include <getopt_long.h>
#endif
char *dbname = NULL;
char *host = NULL;
char *port = NULL;
char *username = NULL;
char *password = NULL;
YesNo prompt_password = DEFAULT;
PGconn *connection = NULL;
static bool parse_pair(const char buffer[], char key[], char value[]);
static char *get_username(void);
/*
* the result is also available with the global variable 'connection'.
*/
void
reconnect(int elevel)
{
StringInfoData buf;
char *new_password;
disconnect();
initStringInfo(&buf);
if (dbname && dbname[0])
appendStringInfo(&buf, "dbname=%s ", dbname);
if (host && host[0])
appendStringInfo(&buf, "host=%s ", host);
if (port && port[0])
appendStringInfo(&buf, "port=%s ", port);
if (username && username[0])
appendStringInfo(&buf, "user=%s ", username);
if (password && password[0])
appendStringInfo(&buf, "password=%s ", password);
connection = pgut_connect(buf.data, prompt_password, elevel);
/* update password */
if (connection)
{
new_password = PQpass(connection);
if (new_password && new_password[0] &&
(password == NULL || strcmp(new_password, password) != 0))
{
free(password);
password = pgut_strdup(new_password);
}
}
termStringInfo(&buf);
}
void
disconnect(void)
{
if (connection)
{
pgut_disconnect(connection);
connection = NULL;
}
}
static void
option_from_env(pgut_option options[])
{
size_t i;
for (i = 0; options && options[i].type; i++)
{
pgut_option *opt = &options[i];
char name[256];
size_t j;
const char *s;
const char *value;
if (opt->source > SOURCE_ENV ||
opt->allowed == SOURCE_DEFAULT || opt->allowed > SOURCE_ENV)
continue;
for (s = opt->lname, j = 0; *s && j < lengthof(name) - 1; s++, j++)
{
if (strchr("-_ ", *s))
name[j] = '_'; /* - to _ */
else
name[j] = toupper(*s);
}
name[j] = '\0';
if ((value = getenv(name)) != NULL)
pgut_setopt(opt, value, SOURCE_ENV);
}
}
/* compare two strings ignore cases and ignore -_ */
bool
pgut_keyeq(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';
}
void
pgut_setopt(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(EINVAL);
}
if (opt->source > src)
{
/* high prior value has been set already. */
return;
}
else if (src >= SOURCE_CMDLINE && opt->source >= src)
{
/* duplicated option in command line */
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;
case 'y':
case 'Y':
if (optarg == NULL)
{
*(YesNo *) opt->var = (opt->type == 'y' ? YES : NO);
return;
}
else
{
bool value;
if (parse_bool(optarg, &value))
{
*(YesNo *) opt->var = (value ? YES : NO);
return;
}
}
message = "a boolean";
break;
default:
ereport(ERROR,
(errcode(EINVAL),
errmsg("invalid option type: %c", opt->type)));
return; /* keep compiler quiet */
}
}
if (isprint(opt->sname))
ereport(ERROR,
(errcode(EINVAL),
errmsg("option -%c, --%s should be %s: '%s'",
opt->sname, opt->lname, message, optarg)));
else
ereport(ERROR,
(errcode(EINVAL),
errmsg("option --%s should be %s: '%s'",
opt->lname, message, optarg)));
}
/*
* 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")) == 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 (pgut_keyeq(key, opt->lname))
{
if (opt->allowed == SOURCE_DEFAULT ||
opt->allowed > SOURCE_FILE)
elog(elevel, "option %s cannot specified in file", opt->lname);
else if (opt->source <= SOURCE_FILE)
pgut_setopt(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;
size_t i;
size_t 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(WARNING, "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(WARNING, "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(WARNING, "syntax error in \"%s\"", buffer);
return false;
}
return true;
}
/*
* execute - Execute a SQL and return the result.
*/
PGresult *
execute(const char *query, int nParams, const char **params)
{
return pgut_execute(connection, query, nParams, params);
}
PGresult *
execute_elevel(const char *query, int nParams, const char **params, int elevel)
{
return pgut_execute_elevel(connection, query, nParams, params, elevel);
}
/*
* command - Execute a SQL and discard the result.
*/
ExecStatusType
command(const char *query, int nParams, const char **params)
{
return pgut_command(connection, query, nParams, params);
}
static void
set_elevel(pgut_option *opt, const char *arg)
{
pgut_log_level = parse_elevel(arg);
}
static pgut_option default_options[] =
{
{ 'b', 'e', "echo" , &pgut_echo },
{ 'f', 'E', "elevel" , set_elevel },
{ 's', 'd', "dbname" , &dbname },
{ 's', 'h', "host" , &host },
{ 's', 'p', "port" , &port },
{ 's', 'U', "username" , &username },
{ 'Y', 'w', "no-password" , &prompt_password },
{ 'y', 'W', "password" , &prompt_password },
{ 0 }
};
static size_t
option_length(const pgut_option opts[])
{
size_t len;
for (len = 0; opts && opts[len].type; len++) { }
return len;
}
static pgut_option *
option_find(int c, pgut_option opts1[], pgut_option opts2[])
{
size_t i;
for (i = 0; opts1 && opts1[i].type; i++)
if (opts1[i].sname == c)
return &opts1[i];
for (i = 0; opts2 && opts2[i].type; i++)
if (opts2[i].sname == c)
return &opts2[i];
return NULL; /* not found */
}
/*
* Returns the current user name.
*/
static char *
get_username(void)
{
char *ret;
#ifndef WIN32
struct passwd *pw;
pw = getpwuid(geteuid());
ret = (pw ? pw->pw_name : NULL);
#else
static char username[128]; /* remains after function execute */
DWORD len = sizeof(username) - 1;
if (GetUserNameA(username, &len))
ret = username;
else
{
_dosmaperr(GetLastError());
ret = NULL;
}
#endif
if (ret == NULL)
ereport(ERROR,
(errcode_errno(),
errmsg("could not get current user name: ")));
return ret;
}
static int
option_has_arg(char type)
{
switch (type)
{
case 'b':
case 'B':
case 'y':
case 'Y':
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 struct option *
option_merge(const pgut_option opts1[], const pgut_option opts2[])
{
struct option *result;
size_t len1 = option_length(opts1);
size_t len2 = option_length(opts2);
size_t n = len1 + len2;
result = pgut_newarray(struct option, n + 1);
option_copy(result, opts1, len1);
option_copy(result + len1, opts2, len2);
memset(&result[n], 0, sizeof(pgut_option));
return result;
}
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;
}
int
pgut_getopt(int argc, char **argv, pgut_option options[])
{
int c;
int optindex = 0;
char *optstring;
struct option *longopts;
pgut_option *opt;
pgut_init(argc, argv);
/* Help message and version are handled at first. */
if (argc > 1)
{
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
help(true);
exit(1);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
printf("%s %s\n", PROGRAM_NAME, PROGRAM_VERSION);
exit(1);
}
if (strcmp(argv[1], "--configuration") == 0)
{
printf("%s\n", PG_VERSION_STR);
exit(0);
}
}
/* Merge default and user options. */
longopts = option_merge(default_options, options);
optstring = longopts_to_optstring(longopts);
/* Assign named options */
while ((c = getopt_long(argc, argv, optstring, longopts, &optindex)) != -1)
{
opt = option_find(c, default_options, options);
pgut_setopt(opt, optarg, SOURCE_CMDLINE);
}
/* Read environment variables */
option_from_env(options);
(void) (dbname ||
(dbname = getenv("PGDATABASE")) ||
(dbname = getenv("PGUSER")) ||
(dbname = get_username()));
return optind;
}
void
help(bool details)
{
pgut_help(details);
if (details)
{
printf("\nConnection options:\n");
printf(" -d, --dbname=DBNAME database to connect\n");
printf(" -h, --host=HOSTNAME database server host or socket directory\n");
printf(" -p, --port=PORT database server port\n");
printf(" -U, --username=USERNAME user name to connect as\n");
printf(" -w, --no-password never prompt for password\n");
printf(" -W, --password force password prompt\n");
}
printf("\nGeneric options:\n");
if (details)
{
printf(" -e, --echo echo queries\n");
printf(" -E, --elevel=LEVEL set output message level\n");
}
printf(" --help show this help, then exit\n");
printf(" --version output version information, then exit\n");
if (details && (PROGRAM_URL || PROGRAM_EMAIL))
{
printf("\n");
if (PROGRAM_URL)
printf("Read the website for details. <%s>\n", PROGRAM_URL);
if (PROGRAM_EMAIL)
printf("Report bugs to <%s>.\n", PROGRAM_EMAIL);
}
}