
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
334 lines
7.1 KiB
C
334 lines
7.1 KiB
C
/*
|
|
* psql - the PostgreSQL interactive terminal
|
|
*
|
|
* Copyright 2000 by PostgreSQL Global Development Group
|
|
*
|
|
* $Header: /cvsroot/pgsql/src/bin/psql/prompt.c,v 1.14 2000/11/13 15:18:14 momjian Exp $
|
|
*/
|
|
#include "postgres.h"
|
|
#include "prompt.h"
|
|
|
|
#include "libpq-fe.h"
|
|
|
|
#include "settings.h"
|
|
#include "common.h"
|
|
#include "variables.h"
|
|
|
|
#ifdef WIN32
|
|
#include <io.h>
|
|
#include <win32.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_UNIX_SOCKETS
|
|
# include <unistd.h>
|
|
# include <netdb.h>
|
|
#endif
|
|
|
|
/*--------------------------
|
|
* get_prompt
|
|
*
|
|
* Returns a statically allocated prompt made by interpolating certain
|
|
* tcsh style escape sequences into pset.vars "PROMPT1|2|3".
|
|
* (might not be completely multibyte safe)
|
|
*
|
|
* Defined interpolations are:
|
|
* %M - database server "hostname.domainname" (or "localhost" if this
|
|
* information is not available)
|
|
* %m - like %M, but hostname only (before first dot)
|
|
* %> - database server port number
|
|
* %n - database user name
|
|
* %/ - current database
|
|
* %~ - like %/ but "~" when database name equals user name
|
|
* %# - "#" if superuser, ">" otherwise
|
|
* %R - in prompt1 normally =, or ^ if single line mode,
|
|
* or a ! if session is not connected to a database;
|
|
* in prompt2 -, *, ', or ";
|
|
* in prompt3 nothing
|
|
* %? - the error code of the last query (not yet implemented)
|
|
* %% - a percent sign
|
|
*
|
|
* %[0-9] - the character with the given decimal code
|
|
* %0[0-7] - the character with the given octal code
|
|
* %0x[0-9A-Fa-f] - the character with the given hexadecimal code
|
|
*
|
|
* %`command` - The result of executing command in /bin/sh with trailing
|
|
* newline stripped.
|
|
* %:name: - The value of the psql variable 'name'
|
|
* (those will not be rescanned for more escape sequences!)
|
|
*
|
|
* If the application-wide prompts became NULL somehow, the returned string
|
|
* will be empty (not NULL!).
|
|
*--------------------------
|
|
*/
|
|
|
|
/*
|
|
* We need hostname information, only if connection is via UNIX socket
|
|
*/
|
|
#ifdef HAVE_UNIX_SOCKETS
|
|
|
|
#define DOMAINNAME 1
|
|
#define HOSTNAME 2
|
|
|
|
/*
|
|
* Return full hostname for localhost.
|
|
* - informations are init only in firts time - not queries DNS or NIS
|
|
* for every localhost() call
|
|
*/
|
|
static char *
|
|
localhost(int type, char *buf, int siz)
|
|
{
|
|
static struct hostent *hp = NULL;
|
|
static int err = 0;
|
|
|
|
if (hp == NULL && err == 0)
|
|
{
|
|
char hname[256];
|
|
|
|
if (gethostname(hname, 256) == 0)
|
|
{
|
|
if (!(hp = gethostbyname(hname)))
|
|
err = 1;
|
|
}
|
|
else
|
|
err = 1;
|
|
}
|
|
|
|
if (hp == NULL)
|
|
return strncpy(buf, "localhost", siz);
|
|
|
|
strncpy(buf, hp->h_name, siz); /* full aaa.bbb.ccc */
|
|
|
|
if (type == HOSTNAME)
|
|
buf[strcspn(buf, ".")] = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
#endif /* HAVE_UNIX_SOCKETS */
|
|
|
|
|
|
char *
|
|
get_prompt(promptStatus_t status)
|
|
{
|
|
#define MAX_PROMPT_SIZE 256
|
|
static char destination[MAX_PROMPT_SIZE + 1];
|
|
char buf[MAX_PROMPT_SIZE + 1];
|
|
bool esc = false;
|
|
const char *p;
|
|
const char *prompt_string;
|
|
|
|
if (status == PROMPT_READY)
|
|
prompt_string = GetVariable(pset.vars, "PROMPT1");
|
|
else if (status == PROMPT_CONTINUE || status == PROMPT_SINGLEQUOTE || status == PROMPT_DOUBLEQUOTE || status == PROMPT_COMMENT || status == PROMPT_PAREN)
|
|
prompt_string = GetVariable(pset.vars, "PROMPT2");
|
|
else if (status == PROMPT_COPY)
|
|
prompt_string = GetVariable(pset.vars, "PROMPT3");
|
|
else
|
|
prompt_string = "? ";
|
|
|
|
|
|
destination[0] = '\0';
|
|
|
|
for (p = prompt_string;
|
|
p && *p && strlen(destination) < MAX_PROMPT_SIZE;
|
|
p++)
|
|
{
|
|
memset(buf, 0, MAX_PROMPT_SIZE + 1);
|
|
if (esc)
|
|
{
|
|
switch (*p)
|
|
{
|
|
case '%':
|
|
strcpy(buf, "%");
|
|
break;
|
|
|
|
/* Current database */
|
|
case '/':
|
|
if (pset.db)
|
|
strncpy(buf, PQdb(pset.db), MAX_PROMPT_SIZE);
|
|
break;
|
|
case '~':
|
|
{
|
|
const char *var;
|
|
|
|
if (pset.db)
|
|
{
|
|
if (strcmp(PQdb(pset.db), PQuser(pset.db)) == 0 ||
|
|
((var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset.db)) == 0))
|
|
strcpy(buf, "~");
|
|
else
|
|
strncpy(buf, PQdb(pset.db), MAX_PROMPT_SIZE);
|
|
}
|
|
break;
|
|
}
|
|
/* DB server hostname (long/short) */
|
|
case 'M':
|
|
case 'm':
|
|
if (pset.db)
|
|
{
|
|
/* INET socket */
|
|
if (PQhost(pset.db))
|
|
{
|
|
strncpy(buf, PQhost(pset.db), MAX_PROMPT_SIZE);
|
|
if (*p == 'm')
|
|
buf[strcspn(buf, ".")] = '\0';
|
|
}
|
|
/* UNIX socket */
|
|
#ifdef HAVE_UNIX_SOCKETS
|
|
else
|
|
{
|
|
if (*p == 'm')
|
|
localhost(HOSTNAME, buf, MAX_PROMPT_SIZE);
|
|
else
|
|
localhost(DOMAINNAME, buf, MAX_PROMPT_SIZE);
|
|
}
|
|
#endif /* HAVE_UNIX_SOCKETS */
|
|
}
|
|
break;
|
|
/* DB server port number */
|
|
case '>':
|
|
if (pset.db && PQport(pset.db))
|
|
strncpy(buf, PQport(pset.db), MAX_PROMPT_SIZE);
|
|
break;
|
|
/* DB server Unix-domain socket */
|
|
case '<':
|
|
if (pset.db && PQunixsocket(pset.db))
|
|
strncpy(buf, PQunixsocket(pset.db), MAX_PROMPT_SIZE);
|
|
break;
|
|
/* DB server user name */
|
|
case 'n':
|
|
if (pset.db)
|
|
strncpy(buf, PQuser(pset.db), MAX_PROMPT_SIZE);
|
|
break;
|
|
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
{
|
|
long int l;
|
|
char *end;
|
|
|
|
l = strtol(p, &end, 0);
|
|
sprintf(buf, "%c", (unsigned char) l);
|
|
p = end - 1;
|
|
break;
|
|
}
|
|
|
|
case 'R':
|
|
switch (status)
|
|
{
|
|
case PROMPT_READY:
|
|
if (!pset.db)
|
|
buf[0] = '!';
|
|
else if (!GetVariableBool(pset.vars, "SINGLELINE"))
|
|
buf[0] = '=';
|
|
else
|
|
buf[0] = '^';
|
|
break;
|
|
case PROMPT_CONTINUE:
|
|
buf[0] = '-';
|
|
break;
|
|
case PROMPT_SINGLEQUOTE:
|
|
buf[0] = '\'';
|
|
break;
|
|
case PROMPT_DOUBLEQUOTE:
|
|
buf[0] = '"';
|
|
break;
|
|
case PROMPT_COMMENT:
|
|
buf[0] = '*';
|
|
break;
|
|
case PROMPT_PAREN:
|
|
buf[0] = '(';
|
|
break;
|
|
default:
|
|
buf[0] = '\0';
|
|
break;
|
|
}
|
|
|
|
case '?':
|
|
/* not here yet */
|
|
break;
|
|
|
|
case '#':
|
|
{
|
|
if (pset.issuper)
|
|
buf[0] = '#';
|
|
else
|
|
buf[0] = '>';
|
|
|
|
break;
|
|
}
|
|
|
|
/* execute command */
|
|
case '`':
|
|
{
|
|
FILE *fd = NULL;
|
|
char *file = strdup(p + 1);
|
|
int cmdend;
|
|
|
|
cmdend = strcspn(file, "`");
|
|
file[cmdend] = '\0';
|
|
if (file)
|
|
fd = popen(file, "r");
|
|
if (fd)
|
|
{
|
|
fgets(buf, MAX_PROMPT_SIZE - 1, fd);
|
|
pclose(fd);
|
|
}
|
|
if (buf[strlen(buf) - 1] == '\n')
|
|
buf[strlen(buf) - 1] = '\0';
|
|
free(file);
|
|
p += cmdend + 1;
|
|
break;
|
|
}
|
|
|
|
/* interpolate variable */
|
|
case ':':
|
|
{
|
|
char *name;
|
|
const char *val;
|
|
int nameend;
|
|
|
|
name = strdup(p + 1);
|
|
nameend = strcspn(name, ":");
|
|
name[nameend] = '\0';
|
|
val = GetVariable(pset.vars, name);
|
|
if (val)
|
|
strncpy(buf, val, MAX_PROMPT_SIZE);
|
|
free(name);
|
|
p += nameend + 1;
|
|
break;
|
|
}
|
|
|
|
|
|
default:
|
|
buf[0] = *p;
|
|
buf[1] = '\0';
|
|
|
|
}
|
|
esc = false;
|
|
}
|
|
else if (*p == '%')
|
|
esc = true;
|
|
else
|
|
{
|
|
buf[0] = *p;
|
|
buf[1] = '\0';
|
|
esc = false;
|
|
}
|
|
|
|
if (!esc)
|
|
strncat(destination, buf, MAX_PROMPT_SIZE - strlen(destination));
|
|
}
|
|
|
|
destination[MAX_PROMPT_SIZE] = '\0';
|
|
return destination;
|
|
}
|