postgresql/src/bin/monitor/monitor.c

1059 lines
23 KiB
C

/*-------------------------------------------------------------------------
*
* monitor.c--
* POSTGRES Terminal Monitor
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/bin/monitor/Attic/monitor.c,v 1.3 1996/07/22 05:59:53 scrappy Exp $
*
*-------------------------------------------------------------------------
*/
#include <errno.h>
#include "libpq/pqsignal.h" /* substitute for <signal.h> */
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <sys/param.h> /* for MAXHOSTNAMELEN on most */
#ifndef WIN32
#include <unistd.h>
#endif
#ifdef PORTNAME_sparc_solaris
#include <netdb.h> /* for MAXHOSTNAMELEN on some */
#endif
#include <sys/types.h>
/* #include <sys/uio.h> */
#include <time.h>
#include "libpq-fe.h"
#include "libpq/libpq-fs.h"
extern char *getenv();
/*
* monitor.c -- function prototypes (all private)
*/
static void do_input(FILE *ifp);
static void init_tmon();
static void welcome();
static void handle_editor();
static void handle_shell();
static void handle_send();
static int handle_execution(char *query);
static void handle_file_insert(FILE *ifp);
static void handle_print();
static void handle_exit(int exit_status);
static void handle_clear();
static void handle_print_time();
static int handle_write_to_file();
static void handle_help();
static void stuff_buffer(char c);
static void argsetup(int *argcP, char ***argvP);
static void handle_copy_out(PGresult *res);
static void handle_copy_in(PGresult *res);
/*
* Functions which maintain the logical query buffer in
* /tmp/PQxxxxx. It in general just does a copy from input
* to query buffer, unless it gets a backslash escape character.
* It recognizes the following escapes:
*
* \e -- enter editor
* \g -- "GO": submit query to POSTGRES
* \i -- include (switch input to external file)
* \p -- print query buffer
* \q -- quit POSTGRES
* \r -- force reset (clear) of query buffer
* \s -- call shell
* \t -- print current time
* \w -- write query buffer to external file
* \h -- print the list of commands
* \? -- print the list of commands
* \\ -- produce a single backslash in query buffer
*
*/
/*
* Declaration of global variables (but only to the file monitor.c
*/
#define DEFAULT_EDITOR "/usr/ucb/vi"
#define COPYBUFSIZ 8192
static char *user_editor; /* user's desired editor */
static int tmon_temp; /* file descriptor for temp. buffer file */
static char *tmon_temp_filename;
static char query_buffer[8192]; /* Max postgres buffer size */
static char *RunOneFile = NULL;
bool RunOneCommand = false;
bool Debugging;
bool Verbose;
bool Silent;
bool TerseOutput = false;
bool PrintAttNames = true;
bool SingleStepMode = false;
bool SemicolonIsGo = true;
#define COLWIDTH 12
extern char *optarg;
extern int optind,opterr;
FILE *debug_port;
/*
* As of release 4, we allow the user to specify options in the environment
* variable PGOPTION. These are treated as command-line options to the
* terminal monitor, and are parsed before the actual command-line args.
* The arge struct is used to construct an argv we can pass to getopt()
* containing the union of the environment and command line arguments.
*/
typedef struct arge {
char *a_arg;
struct arge *a_next;
} arge;
/* the connection to the backend */
PGconn *conn;
void
main(int argc, char **argv)
{
int c;
int errflag = 0;
char *progname;
char *debug_file;
char *dbname;
char *command;
int exit_status = 0;
char errbuf[ERROR_MSG_LENGTH];
char *username, usernamebuf[NAMEDATALEN + 1];
char *pghost = NULL;
char *pgtty = NULL;
char *pgoptions = NULL;
char *pgport = NULL;
int pgtracep = 0;
/*
* Processing command line arguments.
*
* h : sets the hostname.
* p : sets the coom. port
* t : sets the tty.
* o : sets the other options. (see doc/libpq)
* d : enable debugging mode.
* q : run in quiet mode
* Q : run in VERY quiet mode (no output except on errors)
* c : monitor will run one POSTQUEL command and exit
*
* s : step mode (pauses after each command)
* S : don't use semi colon as \g
*
* T : terse mode - no formatting
* N : no attribute names - only columns of data
* (these two options are useful in conjunction with the "-c" option
* in scripts.)
*/
progname = *argv;
Debugging = false;
Verbose = true;
Silent = false;
/* prepend PGOPTION, if any */
argsetup(&argc, &argv);
while ((c = getopt(argc, argv, "a:h:f:p:t:d:qsSTNQc:")) != EOF) {
switch (c) {
case 'a':
fe_setauthsvc(optarg, errbuf);
break;
case 'h' :
pghost = optarg;
break;
case 'f' :
RunOneFile = optarg;
break;
case 'p' :
pgport = optarg;
break;
case 't' :
pgtty = optarg;
break;
case 'T' :
TerseOutput = true;
break;
case 'N' :
PrintAttNames = false;
break;
case 'd' :
/*
* When debugging is turned on, the debugging messages
* will be sent to the specified debug file, which
* can be a tty ..
*/
Debugging = true;
debug_file = optarg;
debug_port = fopen(debug_file,"w+");
if (debug_port == NULL) {
fprintf(stderr,"Unable to open debug file %s \n", debug_file);
exit(1);
}
pgtracep = 1;
break;
case 'q' :
Verbose = false;
break;
case 's' :
SingleStepMode = true;
SemicolonIsGo = true;
break;
case 'S' :
SemicolonIsGo = false;
break;
case 'Q' :
Verbose = false;
Silent = true;
break;
case 'c' :
Verbose = false;
Silent = true;
RunOneCommand = true;
command = optarg;
break;
case '?' :
default :
errflag++;
break;
}
}
if (errflag ) {
fprintf(stderr, "usage: %s [options...] [dbname]\n", progname);
fprintf(stderr, "\t-a authsvc\tset authentication service\n");
fprintf(stderr, "\t-c command\t\texecute one command\n");
fprintf(stderr, "\t-d debugfile\t\tdebugging output file\n");
fprintf(stderr, "\t-h host\t\t\tserver host name\n");
fprintf(stderr, "\t-f file\t\t\trun query from file\n");
fprintf(stderr, "\t-p port\t\t\tserver port number\n");
fprintf(stderr, "\t-q\t\t\tquiet output\n");
fprintf(stderr, "\t-t logfile\t\terror-logging tty\n");
fprintf(stderr, "\t-N\t\t\toutput without attribute names\n");
fprintf(stderr, "\t-Q\t\t\tREALLY quiet output\n");
fprintf(stderr, "\t-T\t\t\tterse output\n");
exit(2);
}
/* Determine our username (according to the authentication system, if
* there is one).
*/
if ((username = fe_getauthname(errbuf)) == (char *) NULL) {
fprintf(stderr, "%s: could not find a valid user name\n",
progname);
exit(2);
}
memset(usernamebuf, 0, sizeof(usernamebuf));
(void) strncpy(usernamebuf, username, NAMEDATALEN);
username = usernamebuf;
/* find database */
if (!(dbname = argv[optind]) &&
!(dbname = getenv("DATABASE")) &&
!(dbname = username)) {
fprintf(stderr, "%s: no database name specified\n", progname);
exit (2);
}
conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbname);
if (PQstatus(conn) == CONNECTION_BAD) {
fprintf(stderr,"Connection to database '%s' failed.\n", dbname);
fprintf(stderr,"%s",PQerrorMessage(conn));
exit(1);
}
if (pgtracep)
PQtrace(conn,debug_port);
/* print out welcome message and start up */
welcome();
init_tmon();
/* parse input */
if (RunOneCommand) {
exit_status = handle_execution(command);
} else if (RunOneFile) {
bool oldVerbose;
FILE *ifp;
if ((ifp = fopen(RunOneFile, "r")) == NULL) {
fprintf(stderr, "Cannot open %s\n", RunOneFile);
}
if (SingleStepMode) {
oldVerbose = Verbose;
Verbose = false;
}
do_input(ifp);
fclose(ifp);
if (SingleStepMode)
Verbose = oldVerbose;
} else {
do_input(stdin);
}
handle_exit(exit_status);
}
static void
do_input(FILE *ifp)
{
int c;
char escape;
/*
* Processing user input.
* Basically we stuff the user input to a temp. file until
* an escape char. is detected, after which we switch
* to the appropriate routine to handle the escape.
*/
if (ifp == stdin) {
if (Verbose)
fprintf(stdout,"\nGo \n* ");
else {
if (!Silent)
fprintf(stdout, "* ");
}
}
while ((c = getc(ifp)) != EOF ) {
if ( c == '\\') {
/* handle escapes */
escape = getc(ifp);
switch( escape ) {
case 'e':
handle_editor();
break;
case 'g':
handle_send();
break;
case 'i':
{
bool oldVerbose;
if (SingleStepMode) {
oldVerbose = Verbose;
Verbose = false;
}
handle_file_insert(ifp);
if (SingleStepMode)
Verbose = oldVerbose;
}
break;
case 'p':
handle_print();
break;
case 'q':
handle_exit(0);
break;
case 'r':
handle_clear();
break;
case 's':
handle_shell();
break;
case 't':
handle_print_time();
break;
case 'w':
handle_write_to_file();
break;
case '?':
case 'h':
handle_help();
break;
case '\\':
c = escape;
stuff_buffer(c);
break;
case ';':
c = escape;
stuff_buffer(c);
break;
default:
fprintf(stderr, "unknown escape given\n");
break;
} /* end-of-switch */
if (ifp == stdin && escape != '\\') {
if (Verbose)
fprintf(stdout,"\nGo \n* ");
else {
if (!Silent)
fprintf(stdout, "* ");
}
}
} else {
stuff_buffer(c);
if (c == ';' && SemicolonIsGo) {
handle_send();
if (Verbose)
fprintf(stdout,"\nGo \n* ");
else {
if (!Silent)
fprintf(stdout, "* ");
}
}
}
}
}
/*
* init_tmon()
*
* set the following :
* user_editor, defaults to DEFAULT_EDITOR if env var is not set
*/
static void
init_tmon()
{
if (!RunOneCommand)
{
char *temp_editor = getenv("EDITOR");
if (temp_editor != NULL)
user_editor = temp_editor;
else
user_editor = DEFAULT_EDITOR;
tmon_temp_filename = malloc(20);
sprintf(tmon_temp_filename, "/tmp/PQ%d", getpid());
tmon_temp = open(tmon_temp_filename,O_CREAT | O_RDWR | O_APPEND,0666);
}
/*
* Catch signals so we can delete the scratch file GK
* but only if we aren't already ignoring them -mer
*/
#ifndef WIN32
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
signal(SIGHUP, handle_exit);
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
signal(SIGQUIT, handle_exit);
#endif /* WIN32 we'll have to figure out how to handle these */
if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
signal(SIGTERM, handle_exit);
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
signal(SIGINT, handle_exit);
}
/*
* welcome simply prints the Postgres welcome mesg.
*/
static
void welcome()
{
if (Verbose) {
fprintf(stdout,"Welcome to the POSTGRES95 terminal monitor\n");
fprintf(stdout," Please read the file COPYRIGHT for copyright terms of POSTGRES95\n");
}
}
/*
* handle_editor()
*
* puts the user into edit mode using the editor specified
* by the variable "user_editor".
*/
static void
handle_editor()
{
char edit_line[100];
close(tmon_temp);
sprintf(edit_line,"%s %s",user_editor,tmon_temp_filename);
system(edit_line);
tmon_temp = open(tmon_temp_filename,O_CREAT | O_RDWR | O_APPEND,0666);
}
static void
handle_shell()
{
char *user_shell;
user_shell = getenv("SHELL");
if (user_shell != NULL) {
system(user_shell);
} else {
system("/bin/sh");
}
}
/*
* handle_send()
*
* This is the routine that initialises the comm. with the
* backend. After the tuples have been returned and
* displayed, the query_buffer is cleared for the
* next query.
*
*/
#include <ctype.h>
static void
handle_send()
{
char c = (char)0;
off_t pos;
int cc = 0;
int i = 0;
pos = lseek(tmon_temp, (off_t) 0, SEEK_SET);
if (pos != 0)
fprintf(stderr, "Bogus file position\n");
if (Verbose)
printf("\n");
/* discard leading white space */
while ( ( cc = read(tmon_temp,&c,1) ) != 0 && isspace((int)c))
continue;
if ( cc != 0 ) {
pos = lseek(tmon_temp, (off_t) -1, SEEK_CUR);
}
if (SingleStepMode) {
char buf[1024];
fprintf(stdout, "\n*******************************************************************************\n");
while ((cc = read(tmon_temp,buf,1024))>0) {
buf[cc] = '\0';
fprintf(stdout, "%s", buf);
}
fprintf(stdout, "\n*******************************************************************************\n\n");
(void)lseek(tmon_temp, (off_t)pos, SEEK_SET);
}
query_buffer[0] = 0;
/*
* Stripping out comments (if any) from the query (should really be
* handled in the parser, of course).
*/
while ( ( cc = read(tmon_temp,&c,1) ) != 0) {
switch(c) {
case '\n':
query_buffer[i++] = ' ';
break;
case '-': {
int temp;
char temp_c;
if ((temp = read(tmon_temp,&temp_c,1)) > 0) {
if (temp_c == '-' ) {
/* read till end of line */
while ((temp = read(tmon_temp,&temp_c,1)) != 0) {
if (temp_c=='\n')
break;
}
}else {
query_buffer[i++] = c;
query_buffer[i++] = temp_c;
}
} else {
query_buffer[i++] = c;
}
break;
}
case '$': {
int temp;
char temp_c[4];
/*
* monitor feature, not POSTGRES SQL. When monitor sees $PWD,
* it will substitute in the current directory.
*/
if ((temp = read(tmon_temp,temp_c,3)) > 0) {
temp_c[temp] = '\0';
if (!strncmp(temp_c, "PWD", 3)) {
int len;
char cwdPath[MAXPATHLEN];
if (getcwd(cwdPath, MAXPATHLEN)==NULL) {
fprintf(stderr,
"cannot get current working directory\n");
break;
}
len = strlen(cwdPath);
query_buffer[i] = '\0';
strcat(query_buffer, cwdPath);
i += len;
} else {
int j;
query_buffer[i++] = c;
for(j = 0; j < temp; j++) {
query_buffer[i++] = temp_c[j];
}
}
} else {
query_buffer[i++] = c;
}
break;
}
default:
query_buffer[i++] = c;
break;
}
}
if (query_buffer[0] == 0) {
query_buffer[0] = ' ';
query_buffer[1] = 0;
}
if (Verbose && !SingleStepMode)
fprintf(stdout,"Query sent to backend is \"%s\"\n", query_buffer);
fflush(stderr);
fflush(stdout);
/*
* Repeat commands until done.
*/
handle_execution(query_buffer);
/* clear the query buffer and temp file -- this is very expensive */
handle_clear();
memset(query_buffer,0,i);
}
/*
* Actually execute the query in *query.
*
* Returns 0 if the query finished successfully, 1 otherwise.
*/
static int
handle_execution(char *query)
{
PGresult *result;
int retval = 0;
result = PQexec(conn, query);
if (result == NULL) {
fprintf(stderr,"%s", PQerrorMessage(conn));
return 1;
}
switch (PQresultStatus(result)) {
case PGRES_EMPTY_QUERY:
break;
case PGRES_COMMAND_OK:
break;
case PGRES_TUPLES_OK:
/* PQprintTuples(result,stdout,PrintAttNames,TerseOutput,COLWIDTH); */
if (TerseOutput)
PQdisplayTuples(result,stdout,1,"",PrintAttNames,TerseOutput);
else
PQdisplayTuples(result,stdout,1,"|",PrintAttNames,TerseOutput);
break;
case PGRES_COPY_OUT:
handle_copy_out(result);
break;
case PGRES_COPY_IN:
handle_copy_in(result);
break;
case PGRES_BAD_RESPONSE:
retval = 1;
break;
case PGRES_NONFATAL_ERROR:
retval = 1;
break;
case PGRES_FATAL_ERROR:
retval = 1;
break;
}
if (SingleStepMode) {
fflush(stdin);
printf("\npress return to continue ...\n");
getc(stdin); /* assume stdin is not a file! */
}
return(retval);
}
/*
* handle_file_insert()
*
* allows the user to insert a query file and execute it.
* NOTE: right now the full path name must be specified.
*/
static void
handle_file_insert(FILE *ifp)
{
char user_filename[50];
FILE *nifp;
fscanf(ifp, "%s",user_filename);
nifp = fopen(user_filename, "r");
if (nifp == (FILE *) NULL) {
fprintf(stderr, "Cannot open %s\n", user_filename);
} else {
do_input(nifp);
fclose (nifp);
}
}
/*
* handle_print()
*
* This routine prints out the contents (query) of the temp. file
* onto stdout.
*/
static void
handle_print()
{
char c;
off_t pos;
int cc;
pos = lseek(tmon_temp, (off_t) 0, SEEK_SET);
if (pos != 0 )
fprintf(stderr, "Bogus file position\n");
printf("\n");
while ( ( cc = read(tmon_temp,&c,1) ) != 0)
putchar(c);
printf("\n");
}
/*
* handle_exit()
*
* ends the comm. with the backend and exit the tm.
*/
static void
handle_exit(int exit_status)
{
if (!RunOneCommand) {
close(tmon_temp);
unlink(tmon_temp_filename);
}
PQfinish(conn);
exit(exit_status);
}
/*
* handle_clear()
*
* This routine clears the temp. file.
*/
static void
handle_clear()
{
/* high cost */
close(tmon_temp);
tmon_temp = open(tmon_temp_filename,O_TRUNC|O_RDWR|O_CREAT ,0666);
}
/*
* handle_print_time()
* prints out the date using the "date" command.
*/
static void
handle_print_time()
{
system("date");
}
/*
* handle_write_to_file()
*
* writes the contents of the temp. file to the
* specified file.
*/
static int
handle_write_to_file()
{
char filename[50];
static char command_line[512];
int status;
status = scanf("%s", filename);
if (status < 1 || !filename[0]) {
fprintf(stderr, "error: filename is empty\n");
return(-1);
}
/* XXX portable way to check return status? $%&! ultrix ... */
(void) sprintf(command_line, "rm -f %s", filename);
(void) system(command_line);
(void) sprintf(command_line, "cp %s %s", tmon_temp_filename, filename);
(void) system(command_line);
return(0);
}
/*
*
* Prints out a help message.
*
*/
static void
handle_help()
{
printf("Available commands include \n\n");
printf("\\e -- enter editor\n");
printf("\\g -- \"GO\": submit query to POSTGRES\n");
printf("\\i -- include (switch input to external file)\n");
printf("\\p -- print query buffer\n");
printf("\\q -- quit POSTGRES\n");
printf("\\r -- force reset (clear) of query buffer\n");
printf("\\s -- shell escape \n");
printf("\\t -- print current time\n");
printf("\\w -- write query buffer to external file\n");
printf("\\h -- print the list of commands\n");
printf("\\? -- print the list of commands\n");
printf("\\\\ -- produce a single backslash in query buffer\n");
fflush(stdin);
}
/*
* stuff_buffer()
*
* writes the user input into the temp. file.
*/
static void
stuff_buffer(char c)
{
int cc;
cc = write(tmon_temp,&c,1);
if(cc == -1)
fprintf(stderr, "error writing to temp file\n");
}
static void
argsetup(int *argcP, char ***argvP)
{
int argc;
char **argv, **curarg;
char *eopts;
char *envopts;
int neopts;
char *start, *end;
arge *head, *tail, *cur;
/* if no options specified in environment, we're done */
if ((envopts = getenv("PGOPTION")) == (char *) NULL)
return;
if ((eopts = (char *) malloc(strlen(envopts) + 1)) == (char *) NULL) {
fprintf(stderr, "cannot malloc copy space for PGOPTION\n");
fflush(stderr);
exit (2);
}
(void) strcpy(eopts, envopts);
/*
* okay, we have PGOPTION from the environment, and we want to treat
* them as user-specified options. to do this, we construct a new
* argv that has argv[0] followed by the arguments from the environment
* followed by the arguments on the command line.
*/
head = cur = (arge *) NULL;
neopts = 0;
for (;;) {
while (isspace(*eopts) && *eopts)
eopts++;
if (*eopts == '\0')
break;
if ((cur = (arge *) malloc(sizeof(arge))) == (arge *) NULL) {
fprintf(stderr, "cannot malloc space for arge\n");
fflush(stderr);
exit (2);
}
end = start = eopts;
if (*start == '"') {
start++;
while (*++end != '\0' && *end != '"')
continue;
if (*end == '\0') {
fprintf(stderr, "unterminated string constant in env var PGOPTION\n");
fflush(stderr);
exit (2);
}
eopts = end + 1;
} else if (*start == '\'') {
start++;
while (*++end != '\0' && *end != '\'')
continue;
if (*end == '\0') {
fprintf(stderr, "unterminated string constant in env var PGOPTION\n");
fflush(stderr);
exit (2);
}
eopts = end + 1;
} else {
while (!isspace(*end) && *end)
end++;
if (isspace(*end))
eopts = end + 1;
else
eopts = end;
}
if (head == (arge *) NULL) {
head = tail = cur;
} else {
tail->a_next = cur;
tail = cur;
}
cur->a_arg = start;
cur->a_next = (arge *) NULL;
*end = '\0';
neopts++;
}
argc = *argcP + neopts;
if ((argv = (char **) malloc(argc * sizeof(char *))) == (char **) NULL) {
fprintf(stderr, "can't malloc space for modified argv\n");
fflush(stderr);
exit (2);
}
curarg = argv;
*curarg++ = *(*argvP)++;
/* copy env args */
while (head != (arge *) NULL) {
cur = head;
*curarg++ = head->a_arg;
head = head->a_next;
free(cur);
}
/* copy rest of args from command line */
while (--(*argcP))
*curarg++ = *(*argvP)++;
/* all done */
*argvP = argv;
*argcP = argc;
}
static void
handle_copy_out(PGresult *res)
{
bool copydone = false;
char copybuf[COPYBUFSIZ];
int ret;
if (!Silent)
fprintf(stdout, "Copy command returns...\n");
while (!copydone) {
ret = PQgetline(res->conn, copybuf, COPYBUFSIZ);
if (copybuf[0] == '.' && copybuf[1] =='\0') {
copydone = true; /* don't print this... */
} else {
fputs(copybuf, stdout);
switch (ret) {
case EOF:
copydone = true;
/*FALLTHROUGH*/
case 0:
fputc('\n', stdout);
break;
case 1:
break;
}
}
}
fflush(stdout);
PQendcopy(res->conn);
}
static void
handle_copy_in(PGresult *res)
{
bool copydone = false;
bool firstload;
bool linedone;
char copybuf[COPYBUFSIZ];
char *s;
int buflen;
int c;
if (!Silent) {
fputs("Enter info followed by a newline\n", stdout);
fputs("End with a dot on a line by itself.\n", stdout);
}
/*
* eat inevitable newline still in input buffer
*
* XXX the 'inevitable newline' is not always present
* for example `cat file | monitor -c "copy from stdin"'
*/
fflush(stdin);
if ((c = getc(stdin)) != '\n' && c != EOF) {
(void) ungetc(c, stdin);
}
while (!copydone) { /* for each input line ... */
if (!Silent) {
fputs(">> ", stdout);
fflush(stdout);
}
firstload = true;
linedone = false;
while (!linedone) { /* for each buffer ... */
s = copybuf;
buflen = COPYBUFSIZ;
for (; buflen > 1 &&
!(linedone = (c = getc(stdin)) == '\n' || c == EOF);
--buflen) {
*s++ = c;
}
if (c == EOF) {
/* reading from stdin, but from a file */
PQputline(res->conn, ".");
copydone = true;
break;
}
*s = '\0';
PQputline(res->conn, copybuf);
if (firstload) {
if (!strcmp(copybuf, ".")) {
copydone = true;
}
firstload = false;
}
}
PQputline(res->conn, "\n");
}
PQendcopy(res->conn);
}