/*------------------------------------------------------------------------- * * 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 #include "libpq/pqsignal.h" /* substitute for */ #include #include #include #include /* for MAXHOSTNAMELEN on most */ #ifndef WIN32 #include #endif #ifdef PORTNAME_sparc_solaris #include /* for MAXHOSTNAMELEN on some */ #endif #include /* #include */ #include #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 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); }