aboutsummaryrefslogtreecommitdiff
path: root/src/bin/monitor/monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/monitor/monitor.c')
-rw-r--r--src/bin/monitor/monitor.c1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/src/bin/monitor/monitor.c b/src/bin/monitor/monitor.c
new file mode 100644
index 00000000000..f9bfa92237b
--- /dev/null
+++ b/src/bin/monitor/monitor.c
@@ -0,0 +1,1058 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.1.1.1 1996/07/09 06:22:13 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);
+}