diff options
Diffstat (limited to 'src/bin/psql/startup.c')
-rw-r--r-- | src/bin/psql/startup.c | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c new file mode 100644 index 00000000000..480e1bc01b6 --- /dev/null +++ b/src/bin/psql/startup.c @@ -0,0 +1,543 @@ +#include <config.h> +#include <c.h> + +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> + +#ifdef WIN32 +#include <io.h> +#include <window.h> +#else +#include <unistd.h> +#endif + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +#include <libpq-fe.h> +#include <pqsignal.h> +#include <version.h> + +#include "settings.h" +#include "command.h" +#include "help.h" +#include "mainloop.h" +#include "common.h" +#include "input.h" +#include "variables.h" +#include "print.h" +#include "describe.h" + + + +static void +process_psqlrc(PsqlSettings * pset); + +static void +showVersion(PsqlSettings *pset, bool verbose); + + +/* Structures to pass information between the option parsing routine + * and the main function + */ +enum _actions { ACT_NOTHING = 0, + ACT_SINGLE_SLASH, + ACT_LIST_DB, + ACT_SHOW_VER, + ACT_SINGLE_QUERY, + ACT_FILE +}; + +struct adhoc_opts { + char * dbname; + char * host; + char * port; + char * username; + enum _actions action; + char * action_string; + bool no_readline; +}; + +static void +parse_options(int argc, char *argv[], PsqlSettings * pset, struct adhoc_opts * options); + + + +/* + * + * main() + * + */ +int +main(int argc, char **argv) +{ + PsqlSettings settings; + struct adhoc_opts options; + int successResult; + + char * username = NULL; + char * password = NULL; + bool need_pass; + + MemSet(&settings, 0, sizeof settings); + + settings.cur_cmd_source = stdin; + settings.cur_cmd_interactive = false; + + settings.vars = CreateVariableSpace(); + settings.popt.topt.format = PRINT_ALIGNED; + settings.queryFout = stdout; + settings.popt.topt.fieldSep = strdup(DEFAULT_FIELD_SEP); + settings.popt.topt.border = 1; + + SetVariable(settings.vars, "prompt1", DEFAULT_PROMPT1); + SetVariable(settings.vars, "prompt2", DEFAULT_PROMPT2); + SetVariable(settings.vars, "prompt3", DEFAULT_PROMPT3); + + settings.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout))); + + /* This is obsolete and will be removed very soon. */ +#ifdef PSQL_ALWAYS_GET_PASSWORDS + settings.getPassword = true; +#else + settings.getPassword = false; +#endif + +#ifdef MULTIBYTE + settings.has_client_encoding = (getenv("PGCLIENTENCODING") != NULL); +#endif + + parse_options(argc, argv, &settings, &options); + + if (options.action==ACT_LIST_DB || options.action==ACT_SHOW_VER) + options.dbname = "template1"; + + if (options.username) { + if (strcmp(options.username, "?")==0) + username = simple_prompt("Username: ", 100, true); + else + username = strdup(options.username); + } + + if (settings.getPassword) + password = simple_prompt("Password: ", 100, false); + + /* loop until we have a password if requested by backend */ + do { + need_pass = false; + settings.db = PQsetdbLogin(options.host, options.port, NULL, NULL, options.dbname, username, password); + + if (PQstatus(settings.db)==CONNECTION_BAD && + strcmp(PQerrorMessage(settings.db), "fe_sendauth: no password supplied\n")==0) { + need_pass = true; + free(password); + password = NULL; + password = simple_prompt("Password: ", 100, false); + } + } while (need_pass); + + free(username); + free(password); + + if (PQstatus(settings.db) == CONNECTION_BAD) { + fprintf(stderr, "Connection to database '%s' failed.\n%s\n", PQdb(settings.db), PQerrorMessage(settings.db)); + PQfinish(settings.db); + exit(EXIT_BADCONN); + } + + if (options.action == ACT_LIST_DB) { + int success = listAllDbs(&settings); + PQfinish(settings.db); + exit (!success); + } + + if (options.action == ACT_SHOW_VER) { + showVersion(&settings, true); + PQfinish(settings.db); + exit (EXIT_SUCCESS); + } + + + if (!GetVariable(settings.vars, "quiet") && !settings.notty && !options.action) + { + puts("Welcome to psql, the PostgreSQL interactive terminal.\n" + "(Please type \\copyright to see the distribution terms of PostgreSQL.)"); + +// showVersion(&settings, false); + + puts("\n" + "Type \\h for help with SQL commands,\n" + " \\? for help on internal slash commands,\n" + " \\q to quit,\n" + " \\g or terminate with semicolon to execute query."); + } + + process_psqlrc(&settings); + + initializeInput(options.no_readline ? 0 : 1); + + /* Now find something to do */ + + /* process file given by -f */ + if (options.action == ACT_FILE) + successResult = process_file(options.action_string, &settings) ? 0 : 1; + /* process slash command if one was given to -c */ + else if (options.action == ACT_SINGLE_SLASH) + successResult = HandleSlashCmds(&settings, options.action_string, NULL, NULL) != CMD_ERROR ? 0 : 1; + /* If the query given to -c was a normal one, send it */ + else if (options.action == ACT_SINGLE_QUERY) + successResult = SendQuery(&settings, options.action_string) ? 0 : 1; + /* or otherwise enter interactive main loop */ + else + successResult = MainLoop(&settings, stdin); + + /* clean up */ + finishInput(); + PQfinish(settings.db); + setQFout(NULL, &settings); + DestroyVariableSpace(settings.vars); + + return successResult; +} + + + +/* + * Parse command line options + */ + +#ifdef WIN32 +/* getopt is not in the standard includes on Win32 */ +int getopt(int, char *const[], const char *); +#endif + +static void +parse_options(int argc, char *argv[], PsqlSettings * pset, struct adhoc_opts * options) +{ +#ifdef HAVE_GETOPT_LONG + static struct option long_options[] = { + { "no-align", no_argument, NULL, 'A' }, + { "command", required_argument, NULL, 'c' }, + { "database", required_argument, NULL, 'd' }, + { "dbname", required_argument, NULL, 'd' }, + { "echo", no_argument, NULL, 'e' }, + { "echo-queries", no_argument, NULL, 'e' }, + { "echo-all", no_argument, NULL, 'E' }, + { "echo-all-queries", no_argument, NULL, 'E' }, + { "file", required_argument, NULL, 'f' }, + { "field-sep", required_argument, NULL, 'F' }, + { "host", required_argument, NULL, 'h' }, + { "html", no_argument, NULL, 'H' }, + { "list", no_argument, NULL, 'l' }, + { "no-readline", no_argument, NULL, 'n' }, + { "out", required_argument, NULL, 'o' }, + { "to-file", required_argument, NULL, 'o' }, + { "port", required_argument, NULL, 'p' }, + { "pset", required_argument, NULL, 'P' }, + { "quiet", no_argument, NULL, 'q' }, + { "single-step", no_argument, NULL, 's' }, + { "single-line", no_argument, NULL, 'S' }, + { "tuples-only", no_argument, NULL, 't' }, + { "table-attr", required_argument, NULL, 'T' }, + { "username", required_argument, NULL, 'U' }, + { "expanded", no_argument, NULL, 'x' }, + { "set", required_argument, NULL, 'v' }, + { "variable", required_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "password", no_argument, NULL, 'W' }, + { "help", no_argument, NULL, '?' }, + }; + + int optindex; +#endif + + extern char *optarg; + extern int optind; + int c; + + MemSet(options, 0, sizeof *options); + +#ifdef HAVE_GETOPT_LONG + while ((c = getopt_long(argc, argv, "Ac:d:eEf:F:lh:Hno:p:P:qsStT:uU:v:VWx?", long_options, &optindex)) != -1) +#else + /* Be sure to leave the '-' in here, so we can catch accidental long options. */ + while ((c = getopt(argc, argv, "Ac:d:eEf:F:lh:Hno:p:P:qsStT:uU:v:VWx?-")) != -1) +#endif + { + switch (c) + { + case 'A': + pset->popt.topt.format = PRINT_UNALIGNED; + break; + case 'c': + options->action_string = optarg; + if (optarg[0] == '\\') + options->action = ACT_SINGLE_SLASH; + else + options->action = ACT_SINGLE_QUERY; + break; + case 'd': + options->dbname = optarg; + break; + case 'e': + SetVariable(pset->vars, "echo", ""); + break; + case 'E': + SetVariable(pset->vars, "echo_secret", ""); + break; + case 'f': + options->action = ACT_FILE; + options->action_string = optarg; + break; + case 'F': + pset->popt.topt.fieldSep = strdup(optarg); + break; + case 'h': + options->host = optarg; + break; + case 'H': + pset->popt.topt.format = PRINT_HTML; + break; + case 'l': + options->action = ACT_LIST_DB; + break; + case 'n': + options->no_readline = true; + break; + case 'o': + setQFout(optarg, pset); + break; + case 'p': + options->port = optarg; + break; + case 'P': + { + char *value; + char *equal_loc; + bool result; + + value = xstrdup(optarg); + equal_loc = strchr(value, '='); + if (!equal_loc) + result = do_pset(value, NULL, &pset->popt, true); + else { + *equal_loc = '\0'; + result = do_pset(value, equal_loc+1, &pset->popt, true); + } + + if (!result) { + fprintf(stderr, "Couldn't set printing paramter %s.\n", value); + exit(EXIT_FAILURE); + } + + free(value); + break; + } + case 'q': + SetVariable(pset->vars, "quiet", ""); + break; + case 's': + SetVariable(pset->vars, "singlestep", ""); + break; + case 'S': + SetVariable(pset->vars, "singleline", ""); + break; + case 't': + pset->popt.topt.tuples_only = true; + break; + case 'T': + pset->popt.topt.tableAttr = xstrdup(optarg); + break; + case 'u': + pset->getPassword = true; + options->username = "?"; + break; + case 'U': + options->username = optarg; + break; + case 'x': + pset->popt.topt.expanded = true; + break; + case 'v': + { + char *value; + char *equal_loc; + + value = xstrdup(optarg); + equal_loc = strchr(value, '='); + if (!equal_loc) { + if (!DeleteVariable(pset->vars, value)) { + fprintf(stderr, "Couldn't delete variable %s.\n", value); + exit(EXIT_FAILURE); + } + } + else { + *equal_loc = '\0'; + if (!SetVariable(pset->vars, value, equal_loc+1)) { + fprintf(stderr, "Couldn't set variable %s to %s.\n", value, equal_loc); + exit(EXIT_FAILURE); + } + } + + free(value); + break; + } + case 'V': + options->action = ACT_SHOW_VER; + break; + case 'W': + pset->getPassword = true; + break; + case '?': + usage(); + exit(EXIT_SUCCESS); + break; +#ifndef HAVE_GETOPT_LONG + case '-': + fprintf(stderr, "This version of psql was compiled without support for long options.\n" + "Use -? for help on invocation options.\n"); + exit(EXIT_FAILURE); + break; +#endif + default: + usage(); + exit(EXIT_FAILURE); + break; + } + } + + /* if we still have arguments, use it as the database name and username */ + while (argc - optind >= 1) { + if (!options->dbname) + options->dbname = argv[optind]; + else if (!options->username) + options->username = argv[optind]; + else + fprintf(stderr, "Warning: extra option %s ignored.\n", argv[optind]); + + optind++; + } +} + + + +/* + * Load /etc/psqlrc or .psqlrc file, if found. + */ +static void +process_psqlrc(PsqlSettings * pset) +{ + char *psqlrc; + char * home; + +#ifdef WIN32 +#define R_OK 0 +#endif + + /* System-wide startup file */ + if (access("/etc/psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, R_OK) == 0) + process_file("/etc/psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, pset); + else if (access("/etc/psqlrc", R_OK) == 0) + process_file("/etc/psqlrc", pset); + + /* Look for one in the home dir */ + home = getenv("HOME"); + + if (home) { + psqlrc = (char *) malloc(strlen(home) + 20); + if (!psqlrc) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + sprintf(psqlrc, "%s/.psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, home); + if (access(psqlrc, R_OK) == 0) + process_file(psqlrc, pset); + else { + sprintf(psqlrc, "%s/.psqlrc", home); + if (access(psqlrc, R_OK) == 0) + process_file(psqlrc, pset); + } + free(psqlrc); + } +} + + + +/* showVersion + * + * Displays the database backend version. + * Also checks against the version psql was compiled for and makes + * sure that there are no problems. + * + * Returns false if there was a problem retrieving the information + * or a mismatch was detected. + */ +static void +showVersion(PsqlSettings *pset, bool verbose) +{ + PGresult *res; + char *versionstr = NULL; + long int release = 0, version = 0, subversion = 0; + + /* get backend version */ + res = PSQLexec(pset, "SELECT version()"); + if (PQresultStatus(res) == PGRES_TUPLES_OK) + versionstr = PQgetvalue(res, 0, 0); + + if (!verbose) { + if (versionstr) puts(versionstr); + PQclear(res); + return; + } + + if (strncmp(versionstr, "PostgreSQL ", 11) == 0) { + char *tmp; + release = strtol(&versionstr[11], &tmp, 10); + version = strtol(tmp+1, &tmp, 10); + subversion = strtol(tmp+1, &tmp, 10); + } + + printf("Server: %s\npsql", versionstr ? versionstr : "(could not connected)"); + + if (strcmp(versionstr, PG_VERSION_STR) != 0) + printf(&PG_VERSION_STR[strcspn(PG_VERSION_STR, " ")]); + printf(" ("__DATE__" "__TIME__")"); + +#ifdef MULTIBYTE + printf(", multibyte"); +#endif +#ifdef HAVE_GETOPT_LONG + printf(", long options"); +#endif +#ifdef USE_READLINE + printf(", readline"); +#endif +#ifdef USE_HISTORY + printf(", history"); +#endif +#ifdef USE_LOCALE + printf(", locale"); +#endif +#ifdef PSQL_ALWAYS_GET_PASSWORDS + printf(", always password"); +#endif +#ifdef USE_ASSERT_CHECKING + printf(", assert checks"); +#endif + + puts(""); + + if (release < 6 || (release == 6 && version < 5)) + puts ("\nWarning: The server you are connected to is potentially too old for this client\n" + "version. You should ideally be using clients and servers from the same\n" + "distribution."); + + PQclear(res); +} |