/*------------------------------------------------------------------------- * * pgut.c * * Copyright (c) 2009-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION * *------------------------------------------------------------------------- */ #include "postgres_fe.h" #include "libpq/pqsignal.h" #include "getopt.h" #include #include #include #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 verbose = false; bool quiet = false; #ifndef PGUT_NO_PROMPT YesNo prompt_password = DEFAULT; #endif /* Database connections */ PGconn *connection = NULL; 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[]); typedef enum { PG_DEBUG, PG_PROGRESS, PG_WARNING, PG_FATAL } eLogType; void pg_log(eLogType type, const char *fmt,...) pg_attribute_printf(2, 3); /* 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); static pgut_option default_options[] = { { 's', 'd', "dbname" , &pgut_dbname, SOURCE_CMDLINE }, { 's', 'h', "host" , &host, SOURCE_CMDLINE }, { 's', 'p', "port" , &port, SOURCE_CMDLINE }, { 'b', 'q', "quiet" , &quiet, SOURCE_CMDLINE }, { 's', 'U', "username" , &username, SOURCE_CMDLINE }, { 'b', 'v', "verbose" , &verbose, SOURCE_CMDLINE }, #ifndef PGUT_NO_PROMPT { 'Y', 'w', "no-password" , &prompt_password, SOURCE_CMDLINE }, { 'y', 'W', "password" , &prompt_password, SOURCE_CMDLINE }, #endif { 0 } }; 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': 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 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 */ } 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; 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: 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; } 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; } 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_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) assign_option(opt, value, SOURCE_ENV); } } int pgut_getopt(int argc, char **argv, pgut_option options[]) { int c; int optindex = 0; char *optstring; struct option *longopts; pgut_option *opt; if (PROGRAM_NAME == NULL) { PROGRAM_NAME = get_progname(argv[0]); set_pglocale_pgservice(argv[0], "pgscripts"); } /* Help message and version are handled at first. */ if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { help(true); exit_or_abort(1); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { fprintf(stderr, "%s %s\n", PROGRAM_NAME, PROGRAM_VERSION); exit_or_abort(1); } } /* 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); 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); } /* Read environment variables */ option_from_env(options); (void) (pgut_dbname || (pgut_dbname = getenv("PGDATABASE")) || (pgut_dbname = getenv("PGUSER")) || (pgut_dbname = get_username())); 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; } #ifndef PGUT_NO_PROMPT /* * 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 } #endif PGconn * pgut_connect(int elevel) { PGconn *conn; if (interrupted && !in_cleanup) elog(ERROR, "interrupted"); #ifndef PGUT_NO_PROMPT if (prompt_password == YES) prompt_for_password(username); #endif /* Start the connection. Loop until we have a password if requested by backend. */ for (;;) { conn = PQsetdbLogin(host, port, NULL, NULL, pgut_dbname, username, password); if (PQstatus(conn) == CONNECTION_OK) return conn; #ifndef PGUT_NO_PROMPT if (conn && PQconnectionNeedsPassword(conn) && prompt_password != NO) { PQfinish(conn); prompt_for_password(username); continue; } #endif elog(elevel, "could not connect to database %s: %s", pgut_dbname, PQerrorMessage(conn)); PQfinish(conn); return NULL; } } void pgut_disconnect(PGconn *conn) { if (conn) { PQfinish(conn); if (conn == connection) connection = NULL; } } /* * the result is also available with the global variable 'connection'. */ PGconn * reconnect_elevel(int elevel) { disconnect(); return connection = pgut_connect(elevel); } void reconnect(void) { reconnect_elevel(ERROR); } void disconnect(void) { if (connection) { PQfinish(connection); connection = NULL; } } /* 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, int elevel) { PGresult *res; if (interrupted && !in_cleanup) elog(ERROR, "interrupted"); /* write query to elog if verbose */ if (verbose) { 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 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(elevel, "query failed: %squery was: %s", PQerrorMessage(conn), query); break; } return res; } void pgut_command(PGconn* conn, const char *query, int nParams, const char **params, int elevel) { PQclear(pgut_execute(conn, query, nParams, params, elevel)); } 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 (verbose) { 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; } PGresult * execute_elevel(const char *query, int nParams, const char **params, int elevel) { return pgut_execute(connection, query, nParams, params, elevel); } /* * execute - Execute a SQL and return the result, or exit_or_abort() if failed. */ PGresult * execute(const char *query, int nParams, const char **params) { return execute_elevel(query, nParams, params, ERROR); } /* * command - Execute a SQL and discard the result, or exit_or_abort() if failed. */ void command(const char *query, int nParams, const char **params) { PQclear(execute(query, nParams, params)); } /* * elog - log to stderr and exit if ERROR or FATAL */ void elog(int elevel, const char *fmt, ...) { va_list args; if (!verbose && elevel <= LOG) return; if (quiet && elevel < WARNING) return; switch (elevel) { case LOG: fputs("LOG: ", stderr); break; case INFO: fputs("INFO: ", stderr); break; case NOTICE: fputs("NOTICE: ", stderr); break; case WARNING: fputs("WARNING: ", stderr); break; case FATAL: fputs("FATAL: ", stderr); break; case PANIC: fputs("PANIC: ", stderr); break; default: if (elevel >= ERROR) fputs("ERROR: ", stderr); break; } va_start(args, fmt); vfprintf(stderr, fmt, args); fputc('\n', stderr); fflush(stderr); va_end(args); if (elevel > 0) exit_or_abort(elevel); } void pg_log(eLogType type, const char *fmt, ...) { va_list args; if (!verbose && type <= PG_PROGRESS) return; if (quiet && type < PG_WARNING) return; switch (type) { case PG_DEBUG: fputs("DEBUG: ", stderr); break; case PG_PROGRESS: fputs("PROGRESS: ", stderr); break; case PG_WARNING: fputs("WARNING: ", stderr); break; case PG_FATAL: fputs("FATAL: ", stderr); break; default: if (type >= PG_FATAL) fputs("ERROR: ", stderr); break; } va_start(args, fmt); vfprintf(stderr, fmt, args); fputc('\n', stderr); fflush(stderr); va_end(args); if (type > 0) exit_or_abort(type); } #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); disconnect(); } static void exit_or_abort(int exitcode) { if (in_cleanup) { /* oops, error in cleanup*/ call_atexit_callbacks(true); abort(); } else { /* normal exit */ exit(exitcode); } } 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"); #ifndef PGUT_NO_PROMPT printf(" -w, --no-password never prompt for password\n"); printf(" -W, --password force password prompt\n"); #endif } printf("\nGeneric options:\n"); if (details) { printf(" -q, --quiet don't write any messages\n"); printf(" -v, --verbose verbose mode\n"); } printf(" --help show this help, then exit\n"); printf(" --version output version information and 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); } } /* * 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 */