PostgreSQL/src/bin/psql/startup.c
Bruce Momjian 2150c2edf1 UUNET is looking into offering PostgreSQL as a part of a managed web
hosting product, on both shared and dedicated machines.  We currently
offer Oracle and MySQL, and it would be a nice middle-ground.
However, as shipped, PostgreSQL lacks the following features we need
that MySQL has:

1. The ability to listen only on a particular IP address.  Each
   hosting customer has their own IP address, on which all of their
   servers (http, ftp, real media, etc.) run.
2. The ability to place the Unix-domain socket in a mode 700 directory.
   This allows us to automatically create an empty database, with an
   empty DBA password, for new or upgrading customers without having
   to interactively set a DBA password and communicate it to (or from)
   the customer.  This in turn cuts down our install and upgrade times.
3. The ability to connect to the Unix-domain socket from within a
   change-rooted environment.  We run CGI programs chrooted to the
   user's home directory, which is another reason why we need to be
   able to specify where the Unix-domain socket is, instead of /tmp.
4. The ability to, if run as root, open a pid file in /var/run as
   root, and then setuid to the desired user.  (mysqld -u can almost
   do this; I had to patch it, too).

The patch below fixes problem 1-3.  I plan to address #4, also, but
haven't done so yet.  These diffs are big enough that they should give
the PG development team something to think about in the meantime :-)
Also, I'm about to leave for 2 weeks' vacation, so I thought I'd get
out what I have, which works (for the problems it tackles), now.

With these changes, we can set up and run PostgreSQL with scripts the
same way we can with apache or proftpd or mysql.

In summary, this patch makes the following enhancements:

1. Adds an environment variable PGUNIXSOCKET, analogous to MYSQL_UNIX_PORT,
   and command line options -k --unix-socket to the relevant programs.
2. Adds a -h option to postmaster to set the hostname or IP address to
   listen on instead of the default INADDR_ANY.
3. Extends some library interfaces to support the above.
4. Fixes a few memory leaks in PQconnectdb().

The default behavior is unchanged from stock 7.0.2; if you don't use
any of these new features, they don't change the operation.

David J. MacKenzie
2000-11-13 15:18:15 +00:00

681 lines
15 KiB
C

/*
* psql - the PostgreSQL interactive terminal
*
* Copyright 2000 by PostgreSQL Global Development Group
*
* $Header: /cvsroot/pgsql/src/bin/psql/startup.c,v 1.38 2000/11/13 15:18:14 momjian Exp $
*/
#include "postgres.h"
#include <sys/types.h>
#ifndef WIN32
#include <unistd.h>
#else /* WIN32 */
#include <io.h>
#include <windows.h>
#include <win32.h>
#endif /* WIN32 */
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include "libpq-fe.h"
#include "command.h"
#include "common.h"
#include "describe.h"
#include "help.h"
#include "input.h"
#include "mainloop.h"
#include "print.h"
#include "settings.h"
#include "variables.h"
#ifdef MULTIBYTE
#include "miscadmin.h"
#include "mb/pg_wchar.h"
#else
/* XXX Grand unified hard-coded badness; this should go into libpq */
#define pg_encoding_to_char(x) "SQL_ASCII"
#endif
/*
* Global psql options
*/
PsqlSettings pset;
/*
* 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_SINGLE_QUERY,
ACT_FILE
};
struct adhoc_opts
{
char *dbname;
char *host;
char *port;
char *unixsocket;
char *username;
enum _actions action;
char *action_string;
bool no_readline;
bool no_psqlrc;
};
static void
parse_psql_options(int argc, char *argv[], struct adhoc_opts * options);
static void
process_psqlrc(void);
static void
showVersion(void);
#ifdef USE_SSL
static void
printSSLInfo(void);
#endif
/*
*
* main
*
*/
int
main(int argc, char *argv[])
{
struct adhoc_opts options;
int successResult;
char *username = NULL;
char *password = NULL;
bool need_pass;
if (!strrchr(argv[0], SEP_CHAR))
pset.progname = argv[0];
else
pset.progname = strrchr(argv[0], SEP_CHAR) + 1;
pset.cur_cmd_source = stdin;
pset.cur_cmd_interactive = false;
pset.encoding = PQenv2encoding();
pset.vars = CreateVariableSpace();
if (!pset.vars)
{
fprintf(stderr, "%s: out of memory\n", pset.progname);
exit(EXIT_FAILURE);
}
pset.popt.topt.format = PRINT_ALIGNED;
pset.queryFout = stdout;
pset.popt.topt.border = 1;
pset.popt.topt.pager = true;
SetVariable(pset.vars, "VERSION", PG_VERSION_STR);
pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout)));
/* This is obsolete and should be removed sometime. */
#ifdef PSQL_ALWAYS_GET_PASSWORDS
pset.getPassword = true;
#else
pset.getPassword = false;
#endif
parse_psql_options(argc, argv, &options);
if (!pset.popt.topt.fieldSep)
pset.popt.topt.fieldSep = xstrdup(DEFAULT_FIELD_SEP);
if (!pset.popt.topt.recordSep)
pset.popt.topt.recordSep = xstrdup(DEFAULT_RECORD_SEP);
if (options.username)
{
/*
* The \001 is a hack to support the deprecated -u option which
* issues a username prompt. The recommended option is -U followed
* by the name on the command line.
*/
if (strcmp(options.username, "\001") == 0)
username = simple_prompt("Username: ", 100, true);
else
username = strdup(options.username);
}
if (pset.getPassword)
password = simple_prompt("Password: ", 100, false);
/* loop until we have a password if requested by backend */
do
{
need_pass = false;
/* FIXME use PQconnectdb to allow setting the unix socket */
pset.db = PQsetdbLogin(options.host, options.port, NULL, NULL,
options.action == ACT_LIST_DB ? "template1" : options.dbname,
username, password);
if (PQstatus(pset.db) == CONNECTION_BAD &&
strcmp(PQerrorMessage(pset.db), "fe_sendauth: no password supplied\n") == 0)
{
PQfinish(pset.db);
need_pass = true;
free(password);
password = NULL;
password = simple_prompt("Password: ", 100, false);
}
} while (need_pass);
free(username);
free(password);
if (PQstatus(pset.db) == CONNECTION_BAD)
{
fprintf(stderr, "%s: %s", pset.progname, PQerrorMessage(pset.db));
PQfinish(pset.db);
exit(EXIT_BADCONN);
}
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
/*
* We need to save the encoding because we want to have it available
* even if the database connection goes bad.
*/
pset.encoding = PQclientEncoding(pset.db);
if (options.action == ACT_LIST_DB)
{
int success = listAllDbs(false);
PQfinish(pset.db);
exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
}
SetVariable(pset.vars, "DBNAME", PQdb(pset.db));
SetVariable(pset.vars, "USER", PQuser(pset.db));
SetVariable(pset.vars, "HOST", PQhost(pset.db));
SetVariable(pset.vars, "PORT", PQport(pset.db));
SetVariable(pset.vars, "UNIXSOCKET", PQunixsocket(pset.db));
SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
#ifndef WIN32
pqsignal(SIGINT, handle_sigint); /* control-C => cancel */
#endif
/*
* Now find something to do
*/
/*
* process file given by -f
*/
if (options.action == ACT_FILE)
{
if (!options.no_psqlrc)
process_psqlrc();
successResult = process_file(options.action_string);
}
/*
* process slash command if one was given to -c
*/
else if (options.action == ACT_SINGLE_SLASH)
{
const char *value;
if ((value = GetVariable(pset.vars, "ECHO")) && strcmp(value, "all") == 0)
puts(options.action_string);
successResult = HandleSlashCmds(options.action_string, NULL, NULL) != CMD_ERROR
? EXIT_SUCCESS : EXIT_FAILURE;
}
/*
* If the query given to -c was a normal one, send it
*/
else if (options.action == ACT_SINGLE_QUERY)
{
const char *value;
if ((value = GetVariable(pset.vars, "ECHO")) && strcmp(value, "all") == 0)
puts(options.action_string);
successResult = SendQuery(options.action_string)
? EXIT_SUCCESS : EXIT_FAILURE;
}
/*
* or otherwise enter interactive main loop
*/
else
{
pset.issuper = test_superuser(PQuser(pset.db));
if (!QUIET() && !pset.notty)
{
printf("Welcome to %s, the PostgreSQL interactive terminal.\n\n"
"Type: \\copyright for distribution terms\n"
" \\h for help with SQL commands\n"
" \\? for help on internal slash commands\n"
" \\g or terminate with semicolon to execute query\n"
" \\q to quit\n\n", pset.progname);
#ifdef USE_SSL
printSSLInfo();
#endif
}
SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2);
SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3);
if (!options.no_psqlrc)
process_psqlrc();
if (!pset.notty)
initializeInput(options.no_readline ? 0 : 1);
successResult = MainLoop(stdin);
}
/* clean up */
PQfinish(pset.db);
setQFout(NULL);
return successResult;
}
/*
* Parse command line options
*/
#ifdef WIN32
/* getopt is not in the standard includes on Win32 */
int getopt(int, char *const[], const char *);
/* And it requires progname to be set */
char *__progname = "psql";
#endif
static void
parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
{
#ifdef HAVE_GETOPT_LONG
static struct option long_options[] =
{
{"echo-all", no_argument, NULL, 'a'},
{"no-align", no_argument, NULL, 'A'},
{"command", required_argument, NULL, 'c'},
{"dbname", required_argument, NULL, 'd'},
{"echo-queries", no_argument, NULL, 'e'},
{"echo-hidden", no_argument, NULL, 'E'},
{"file", required_argument, NULL, 'f'},
{"field-separator", required_argument, NULL, 'F'},
{"host", required_argument, NULL, 'h'},
{"html", no_argument, NULL, 'H'},
{"unixsocket", required_argument, NULL, 'k'},
{"list", no_argument, NULL, 'l'},
{"no-readline", no_argument, NULL, 'n'},
{"output", required_argument, NULL, 'o'},
{"port", required_argument, NULL, 'p'},
{"pset", required_argument, NULL, 'P'},
{"quiet", no_argument, NULL, 'q'},
{"record-separator", required_argument, NULL, 'R'},
{"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'},
{"set", required_argument, NULL, 'v'},
{"variable", required_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},
{"password", no_argument, NULL, 'W'},
{"expanded", no_argument, NULL, 'x'},
{"no-psqlrc", no_argument, NULL, 'X'},
{"help", no_argument, NULL, '?'},
};
int optindex;
#endif /* HAVE_GETOPT_LONG */
extern char *optarg;
extern int optind;
int c;
bool used_old_u_option = false;
memset(options, 0, sizeof *options);
#ifdef HAVE_GETOPT_LONG
while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:lh:Hk:no:p:P:qRsStT:uU:v:VWxX?", long_options, &optindex)) != -1)
#else /* not HAVE_GETOPT_LONG */
/*
* Be sure to leave the '-' in here, so we can catch accidental long
* options.
*/
while ((c = getopt(argc, argv, "aAc:d:eEf:F:lh:Hk:no:p:P:qRsStT:uU:v:VWxX?-")) != -1)
#endif /* not HAVE_GETOPT_LONG */
{
switch (c)
{
case 'a':
SetVariable(pset.vars, "ECHO", "all");
break;
case 'A':
pset.popt.topt.format = PRINT_UNALIGNED;
break;
case 'c':
options->action_string = optarg;
if (optarg[0] == '\\')
{
options->action = ACT_SINGLE_SLASH;
options->action_string++;
}
else
options->action = ACT_SINGLE_QUERY;
break;
case 'd':
options->dbname = optarg;
break;
case 'e':
SetVariable(pset.vars, "ECHO", "queries");
break;
case 'E':
SetVariableBool(pset.vars, "ECHO_HIDDEN");
break;
case 'f':
options->action = ACT_FILE;
options->action_string = optarg;
break;
case 'F':
pset.popt.topt.fieldSep = xstrdup(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 'k':
options->unixsocket = optarg;
break;
case 'n':
options->no_readline = true;
break;
case 'o':
setQFout(optarg);
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, "%s: couldn't set printing parameter %s\n", pset.progname, value);
exit(EXIT_FAILURE);
}
free(value);
break;
}
case 'q':
SetVariableBool(pset.vars, "QUIET");
break;
case 'R':
pset.popt.topt.recordSep = xstrdup(optarg);
break;
case 's':
SetVariableBool(pset.vars, "SINGLESTEP");
break;
case 'S':
SetVariableBool(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 = "\001"; /* hopefully nobody has
* that username */
/* this option is out */
used_old_u_option = true;
break;
case 'U':
options->username = optarg;
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, "%s: could not delete variable %s\n",
pset.progname, value);
exit(EXIT_FAILURE);
}
}
else
{
*equal_loc = '\0';
if (!SetVariable(pset.vars, value, equal_loc + 1))
{
fprintf(stderr, "%s: could not set variable %s\n",
pset.progname, value);
exit(EXIT_FAILURE);
}
}
free(value);
break;
}
case 'V':
showVersion();
exit(EXIT_SUCCESS);
case 'W':
pset.getPassword = true;
break;
case 'x':
pset.popt.topt.expanded = true;
break;
case 'X':
options->no_psqlrc = true;
break;
case '?':
/* Actual help option given */
if (strcmp(argv[optind - 1], "-?") == 0 || strcmp(argv[optind - 1], "--help") == 0)
{
usage();
exit(EXIT_SUCCESS);
}
/* unknown option reported by getopt */
else
{
fputs("Try -? for help.\n", stderr);
exit(EXIT_FAILURE);
}
break;
#ifndef HAVE_GETOPT_LONG
case '-':
fprintf(stderr, "%s was compiled without support for long options.\n"
"Use -? for help on invocation options.\n", pset.progname);
exit(EXIT_FAILURE);
break;
#endif
default:
fputs("Try -? for help.\n", stderr);
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 if (!QUIET())
fprintf(stderr, "%s: warning: extra option %s ignored\n",
pset.progname, argv[optind]);
optind++;
}
if (used_old_u_option && !QUIET())
fprintf(stderr, "%s: Warning: The -u option is deprecated. Use -U.\n", pset.progname);
}
/*
* Load .psqlrc file, if found.
*/
static void
process_psqlrc(void)
{
char *psqlrc;
char *home;
#ifdef WIN32
#define R_OK 0
#endif
/* Look for one in the home dir */
home = getenv("HOME");
if (home)
{
psqlrc = malloc(strlen(home) + 20);
if (!psqlrc)
{
fprintf(stderr, "%s: out of memory\n", pset.progname);
exit(EXIT_FAILURE);
}
sprintf(psqlrc, "%s/.psqlrc-" PG_VERSION, home);
if (access(psqlrc, R_OK) == 0)
process_file(psqlrc);
else
{
sprintf(psqlrc, "%s/.psqlrc", home);
if (access(psqlrc, R_OK) == 0)
process_file(psqlrc);
}
free(psqlrc);
}
}
/* showVersion
*
* This output format is intended to match GNU standards.
*/
static void
showVersion(void)
{
puts("psql (PostgreSQL) " PG_VERSION);
#if defined(USE_READLINE) || defined (USE_HISTORY) || defined(MULTIBYTE)
fputs("contains ", stdout);
#ifdef USE_READLINE
fputs("readline", stdout);
#define _Feature
#endif
#ifdef USE_HISTORY
#ifdef _Feature
fputs(", ", stdout);
#else
#define _Feature
#endif
fputs("history", stdout);
#endif
#ifdef MULTIBYTE
#ifdef _Feature
fputs(", ", stdout);
#else
#define _Feature
#endif
fputs("multibyte", stdout);
#endif
#undef _Feature
puts(" support");
#endif
puts("Portions Copyright (c) 1996-2000, PostgreSQL, Inc");
puts("Portions Copyright (c) 1996 Regents of the University of California");
puts("Read the file COPYRIGHT or use the command \\copyright to see the");
puts("usage and distribution terms.");
}
/*
* printSSLInfo
*
* Prints information about the current SSL connection, if SSL is in use
*/
#ifdef USE_SSL
static void
printSSLInfo(void)
{
int sslbits = -1;
SSL *ssl;
ssl = PQgetssl(pset.db);
if (!ssl)
return; /* no SSL */
SSL_get_cipher_bits(ssl, &sslbits);
printf("SSL enabled connection. Chiper: %s, bits: %i\n\n",
SSL_get_cipher(ssl),sslbits);
}
#endif