]> granicus.if.org Git - postgresql/blob - src/bin/psql/common.c
Add popen/pclose -> _popen/_pclose() mapping for Win32.
[postgresql] / src / bin / psql / common.c
1 /*
2  * psql - the PostgreSQL interactive terminal
3  *
4  * Copyright 2000 by PostgreSQL Global Development Group
5  *
6  * $Header: /cvsroot/pgsql/src/bin/psql/common.c,v 1.51 2002/10/29 19:35:33 momjian Exp $
7  */
8 #include "postgres_fe.h"
9 #include "common.h"
10
11 #include <errno.h>
12 #include <stdarg.h>
13 #ifndef HAVE_STRDUP
14 #include <strdup.h>
15 #endif
16 #include <signal.h>
17 #ifndef WIN32
18 #include <sys/time.h>
19 #include <unistd.h>                             /* for write() */
20 #include <setjmp.h>
21 #else
22 #include <io.h>                                 /* for _write() */
23 #include <win32.h>
24 #include <sys/timeb.h>                  /* for _ftime() */
25 #endif
26
27 #ifndef WIN32
28 #include <sys/ioctl.h>                  /* for ioctl() */
29 #endif
30
31 #ifdef HAVE_TERMIOS_H
32 #include <termios.h>
33 #endif
34
35 #include "libpq-fe.h"
36 #include "pqsignal.h"
37
38 #include "settings.h"
39 #include "variables.h"
40 #include "copy.h"
41 #include "prompt.h"
42 #include "print.h"
43 #include "mainloop.h"
44
45 extern bool prompt_state;
46
47 /*
48  * "Safe" wrapper around strdup()
49  */
50 char *
51 xstrdup(const char *string)
52 {
53         char       *tmp;
54
55         if (!string)
56         {
57                 fprintf(stderr, gettext("%s: xstrdup: cannot duplicate null pointer (internal error)\n"),
58                                 pset.progname);
59                 exit(EXIT_FAILURE);
60         }
61         tmp = strdup(string);
62         if (!tmp)
63         {
64                 psql_error("out of memory\n");
65                 exit(EXIT_FAILURE);
66         }
67         return tmp;
68 }
69
70
71
72 /*
73  * setQFout
74  * -- handler for -o command line option and \o command
75  *
76  * Tries to open file fname (or pipe if fname starts with '|')
77  * and stores the file handle in pset)
78  * Upon failure, sets stdout and returns false.
79  */
80 bool
81 setQFout(const char *fname)
82 {
83         bool            status = true;
84
85         /* Close old file/pipe */
86         if (pset.queryFout && pset.queryFout != stdout && pset.queryFout != stderr)
87         {
88                 if (pset.queryFoutPipe)
89                         pclose(pset.queryFout);
90                 else
91                         fclose(pset.queryFout);
92         }
93
94         /* If no filename, set stdout */
95         if (!fname || fname[0] == '\0')
96         {
97                 pset.queryFout = stdout;
98                 pset.queryFoutPipe = false;
99         }
100         else if (*fname == '|')
101         {
102                 pset.queryFout = popen(fname + 1, "w");
103                 pset.queryFoutPipe = true;
104         }
105         else
106         {
107                 pset.queryFout = fopen(fname, "w");
108                 pset.queryFoutPipe = false;
109         }
110
111         if (!(pset.queryFout))
112         {
113                 psql_error("%s: %s\n", fname, strerror(errno));
114                 pset.queryFout = stdout;
115                 pset.queryFoutPipe = false;
116                 status = false;
117         }
118
119         /* Direct signals */
120 #ifndef WIN32
121         if (pset.queryFoutPipe)
122                 pqsignal(SIGPIPE, SIG_IGN);
123         else
124                 pqsignal(SIGPIPE, SIG_DFL);
125 #endif
126
127         return status;
128 }
129
130
131
132 /*
133  * Error reporting for scripts. Errors should look like
134  *       psql:filename:lineno: message
135  *
136  */
137 void
138 psql_error(const char *fmt,...)
139 {
140         va_list         ap;
141
142         fflush(stdout);
143         if (pset.queryFout != stdout)
144                 fflush(pset.queryFout);
145
146         if (pset.inputfile)
147                 fprintf(stderr, "%s:%s:%u: ", pset.progname, pset.inputfile, pset.lineno);
148         va_start(ap, fmt);
149         vfprintf(stderr, gettext(fmt), ap);
150         va_end(ap);
151 }
152
153
154
155 /*
156  * for backend INFO, WARNING, ERROR
157  */
158 void
159 NoticeProcessor(void *arg, const char *message)
160 {
161         (void) arg;                                     /* not used */
162         psql_error("%s", message);
163 }
164
165
166
167 /*
168  * Code to support query cancellation
169  *
170  * Before we start a query, we enable a SIGINT signal catcher that sends a
171  * cancel request to the backend. Note that sending the cancel directly from
172  * the signal handler is safe because PQrequestCancel() is written to make it
173  * so. We use write() to print to stdout because it's better to use simple
174  * facilities in a signal handler.
175  */
176 PGconn     *cancelConn;
177 volatile bool cancel_pressed;
178
179 #ifndef WIN32
180
181 #define write_stderr(String) write(fileno(stderr), String, strlen(String))
182
183 void
184 handle_sigint(SIGNAL_ARGS)
185 {
186         int                     save_errno = errno;
187
188         /* Don't muck around if copying in or prompting for a password. */
189         if ((copy_in_state && pset.cur_cmd_interactive) || prompt_state)
190                 return;
191
192         if (cancelConn == NULL)
193                 siglongjmp(main_loop_jmp, 1);
194
195         cancel_pressed = true;
196
197         if (PQrequestCancel(cancelConn))
198                 write_stderr("Cancel request sent\n");
199         else
200         {
201                 write_stderr("Could not send cancel request: ");
202                 write_stderr(PQerrorMessage(cancelConn));
203         }
204         errno = save_errno;                     /* just in case the write changed it */
205 }
206 #endif   /* not WIN32 */
207
208
209 /*
210  * PSQLexec
211  *
212  * This is the way to send "backdoor" queries (those not directly entered
213  * by the user). It is subject to -E but not -e.
214  *
215  * If the given querystring generates multiple PGresults, normally the last
216  * one is returned to the caller.  However, if ignore_command_ok is TRUE,
217  * then PGresults with status PGRES_COMMAND_OK are ignored.  This is intended
218  * mainly to allow locutions such as "begin; select ...; commit".
219  */
220 PGresult *
221 PSQLexec(const char *query, bool ignore_command_ok)
222 {
223         PGresult   *res = NULL;
224         PGresult   *newres;
225         const char *var;
226         ExecStatusType rstatus;
227
228         if (!pset.db)
229         {
230                 psql_error("You are currently not connected to a database.\n");
231                 return NULL;
232         }
233
234         var = GetVariable(pset.vars, "ECHO_HIDDEN");
235         if (var)
236         {
237                 printf("********* QUERY **********\n"
238                            "%s\n"
239                            "**************************\n\n", query);
240                 fflush(stdout);
241         }
242
243         if (var && strcmp(var, "noexec") == 0)
244                 return NULL;
245
246         /* discard any uneaten results of past queries */
247         while ((newres = PQgetResult(pset.db)) != NULL)
248                 PQclear(newres);
249
250         cancelConn = pset.db;
251         if (PQsendQuery(pset.db, query))
252         {
253                 while ((newres = PQgetResult(pset.db)) != NULL)
254                 {
255                         rstatus = PQresultStatus(newres);
256                         if (ignore_command_ok && rstatus == PGRES_COMMAND_OK)
257                         {
258                                 PQclear(newres);
259                                 continue;
260                         }
261                         PQclear(res);
262                         res = newres;
263                         if (rstatus == PGRES_COPY_IN ||
264                                 rstatus == PGRES_COPY_OUT)
265                                 break;
266                 }
267         }
268         rstatus = PQresultStatus(res);
269         /* keep cancel connection for copy out state */
270         if (rstatus != PGRES_COPY_OUT)
271                 cancelConn = NULL;
272         if (rstatus == PGRES_COPY_IN)
273                 copy_in_state = true;
274
275         if (res && (rstatus == PGRES_COMMAND_OK ||
276                                 rstatus == PGRES_TUPLES_OK ||
277                                 rstatus == PGRES_COPY_IN ||
278                                 rstatus == PGRES_COPY_OUT))
279                 return res;
280         else
281         {
282                 psql_error("%s", PQerrorMessage(pset.db));
283                 PQclear(res);
284
285                 if (PQstatus(pset.db) == CONNECTION_BAD)
286                 {
287                         if (!pset.cur_cmd_interactive)
288                         {
289                                 psql_error("connection to server was lost\n");
290                                 exit(EXIT_BADCONN);
291                         }
292                         fputs(gettext("The connection to the server was lost. Attempting reset: "), stderr);
293                         PQreset(pset.db);
294                         if (PQstatus(pset.db) == CONNECTION_BAD)
295                         {
296                                 fputs(gettext("Failed.\n"), stderr);
297                                 PQfinish(pset.db);
298                                 pset.db = NULL;
299                                 SetVariable(pset.vars, "DBNAME", NULL);
300                                 SetVariable(pset.vars, "HOST", NULL);
301                                 SetVariable(pset.vars, "PORT", NULL);
302                                 SetVariable(pset.vars, "USER", NULL);
303                                 SetVariable(pset.vars, "ENCODING", NULL);
304                         }
305                         else
306                                 fputs(gettext("Succeeded.\n"), stderr);
307                 }
308
309                 return NULL;
310         }
311 }
312
313
314
315 /*
316  * SendQuery: send the query string to the backend
317  * (and print out results)
318  *
319  * Note: This is the "front door" way to send a query. That is, use it to
320  * send queries actually entered by the user. These queries will be subject to
321  * single step mode.
322  * To send "back door" queries (generated by slash commands, etc.) in a
323  * controlled way, use PSQLexec().
324  *
325  * Returns true if the query executed successfully, false otherwise.
326  */
327 bool
328 SendQuery(const char *query)
329 {
330         bool            success = false;
331         PGresult   *results;
332         PGnotify   *notify;
333 #ifndef WIN32
334         struct timeval before,
335                                 after;
336 #else
337         struct _timeb before,
338                                 after;
339 #endif
340
341         if (!pset.db)
342         {
343                 psql_error("You are currently not connected to a database.\n");
344                 return false;
345         }
346
347         if (GetVariableBool(pset.vars, "SINGLESTEP"))
348         {
349                 char            buf[3];
350
351                 printf(gettext("***(Single step mode: Verify query)*********************************************\n"
352                                            "%s\n"
353                                            "***(press return to proceed or enter x and return to cancel)********************\n"),
354                            query);
355                 fflush(stdout);
356                 if (fgets(buf, sizeof(buf), stdin) != NULL)
357                         if (buf[0] == 'x')
358                                 return false;
359         }
360         else
361         {
362                 const char *var = GetVariable(pset.vars, "ECHO");
363
364                 if (var && strncmp(var, "queries", strlen(var)) == 0)
365                         puts(query);
366         }
367
368         cancelConn = pset.db;
369
370 #ifndef WIN32
371         if (pset.timing)
372                 gettimeofday(&before, NULL);
373         results = PQexec(pset.db, query);
374         if (pset.timing)
375                 gettimeofday(&after, NULL);
376 #else
377         if (pset.timing)
378                 _ftime(&before);
379         results = PQexec(pset.db, query);
380         if (pset.timing)
381                 _ftime(&after);
382 #endif
383
384         if (PQresultStatus(results) == PGRES_COPY_IN)
385                 copy_in_state = true;
386         /* keep cancel connection for copy out state */
387         if (PQresultStatus(results) != PGRES_COPY_OUT)
388                 cancelConn = NULL;
389
390         if (results == NULL)
391         {
392                 fputs(PQerrorMessage(pset.db), pset.queryFout);
393                 success = false;
394         }
395         else
396         {
397                 switch (PQresultStatus(results))
398                 {
399                         case PGRES_TUPLES_OK:
400                                 /* write output to \g argument, if any */
401                                 if (pset.gfname)
402                                 {
403                                         FILE       *queryFout_copy = pset.queryFout;
404                                         bool            queryFoutPipe_copy = pset.queryFoutPipe;
405
406                                         pset.queryFout = stdout;        /* so it doesn't get
407                                                                                                  * closed */
408
409                                         /* open file/pipe */
410                                         if (!setQFout(pset.gfname))
411                                         {
412                                                 pset.queryFout = queryFout_copy;
413                                                 pset.queryFoutPipe = queryFoutPipe_copy;
414                                                 success = false;
415                                                 break;
416                                         }
417
418                                         printQuery(results, &pset.popt, pset.queryFout);
419
420                                         /* close file/pipe, restore old setting */
421                                         setQFout(NULL);
422
423                                         pset.queryFout = queryFout_copy;
424                                         pset.queryFoutPipe = queryFoutPipe_copy;
425
426                                         free(pset.gfname);
427                                         pset.gfname = NULL;
428
429                                         success = true;
430                                 }
431                                 else
432                                 {
433                                         printQuery(results, &pset.popt, pset.queryFout);
434                                         success = true;
435                                 }
436                                 break;
437                         case PGRES_EMPTY_QUERY:
438                                 success = true;
439                                 break;
440                         case PGRES_COMMAND_OK:
441                                 {
442                                         char            buf[10];
443
444                                         success = true;
445                                         sprintf(buf, "%u", (unsigned int) PQoidValue(results));
446                                         if (!QUIET())
447                                                 fprintf(pset.queryFout, "%s\n", PQcmdStatus(results));
448                                         SetVariable(pset.vars, "LASTOID", buf);
449                                         break;
450                                 }
451                         case PGRES_COPY_OUT:
452                                 success = handleCopyOut(pset.db, pset.queryFout);
453                                 break;
454
455                         case PGRES_COPY_IN:
456                                 if (pset.cur_cmd_interactive && !QUIET())
457                                         puts(gettext("Enter data to be copied followed by a newline.\n"
458                                                                  "End with a backslash and a period on a line by itself."));
459
460                                 success = handleCopyIn(pset.db, pset.cur_cmd_source,
461                                                                            pset.cur_cmd_interactive ? get_prompt(PROMPT_COPY) : NULL);
462                                 break;
463
464                         case PGRES_NONFATAL_ERROR:
465                         case PGRES_FATAL_ERROR:
466                         case PGRES_BAD_RESPONSE:
467                                 success = false;
468                                 psql_error("%s", PQerrorMessage(pset.db));
469                                 break;
470                 }
471
472                 fflush(pset.queryFout);
473
474                 if (PQstatus(pset.db) == CONNECTION_BAD)
475                 {
476                         if (!pset.cur_cmd_interactive)
477                         {
478                                 psql_error("connection to server was lost\n");
479                                 exit(EXIT_BADCONN);
480                         }
481                         fputs(gettext("The connection to the server was lost. Attempting reset: "), stderr);
482                         PQreset(pset.db);
483                         if (PQstatus(pset.db) == CONNECTION_BAD)
484                         {
485                                 fputs(gettext("Failed.\n"), stderr);
486                                 PQfinish(pset.db);
487                                 PQclear(results);
488                                 pset.db = NULL;
489                                 SetVariable(pset.vars, "DBNAME", NULL);
490                                 SetVariable(pset.vars, "HOST", NULL);
491                                 SetVariable(pset.vars, "PORT", NULL);
492                                 SetVariable(pset.vars, "USER", NULL);
493                                 SetVariable(pset.vars, "ENCODING", NULL);
494                                 return false;
495                         }
496                         else
497                                 fputs(gettext("Succeeded.\n"), stderr);
498                 }
499
500                 /* check for asynchronous notification returns */
501                 while ((notify = PQnotifies(pset.db)) != NULL)
502                 {
503                         fprintf(pset.queryFout, gettext("Asynchronous NOTIFY '%s' from backend with pid %d received.\n"),
504                                         notify->relname, notify->be_pid);
505                         free(notify);
506                         fflush(pset.queryFout);
507                 }
508
509                 if (results)
510                         PQclear(results);
511         }
512
513         /* Possible microtiming output */
514         if (pset.timing && success)
515 #ifndef WIN32
516                 printf(gettext("Time: %.2f ms\n"),
517                            ((after.tv_sec - before.tv_sec) * 1000000.0 + after.tv_usec - before.tv_usec) / 1000.0);
518 #else
519                 printf(gettext("Time: %.2f ms\n"),
520                            ((after.time - before.time) * 1000.0 + after.millitm - before.millitm));
521 #endif
522
523         return success;
524 }
525
526
527 /*
528  * PageOutput
529  *
530  * Tests if pager is needed and returns appropriate FILE pointer.
531  */
532 FILE *
533 PageOutput(int lines, bool pager)
534 {
535         /* check whether we need / can / are supposed to use pager */
536         if (pager
537 #ifndef WIN32
538                 &&
539                 isatty(fileno(stdin)) &&
540                 isatty(fileno(stdout))
541 #endif
542                 )
543         {
544                 const char *pagerprog;
545
546 #ifdef TIOCGWINSZ
547                 int                     result;
548                 struct winsize screen_size;
549
550                 result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
551                 if (result == -1 || lines > screen_size.ws_row)
552                 {
553 #endif
554                         pagerprog = getenv("PAGER");
555                         if (!pagerprog)
556                                 pagerprog = DEFAULT_PAGER;
557 #ifndef WIN32
558                         pqsignal(SIGPIPE, SIG_IGN);
559 #endif
560                         return popen(pagerprog, "w");
561 #ifdef TIOCGWINSZ
562                 }
563 #endif
564         }
565
566         return stdout;
567 }