diff options
Diffstat (limited to 'src/bin/monitor/monitor.c')
-rw-r--r-- | src/bin/monitor/monitor.c | 1058 |
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); +} |