summaryrefslogtreecommitdiff
path: root/src/bin/psql/command.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/command.c')
-rw-r--r--src/bin/psql/command.c1228
1 files changed, 1228 insertions, 0 deletions
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
new file mode 100644
index 00000000000..6bcf96c9cda
--- /dev/null
+++ b/src/bin/psql/command.c
@@ -0,0 +1,1228 @@
+#include <config.h>
+#include <c.h>
+#include "command.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#ifndef WIN32
+#include <sys/types.h> /* for umask() */
+#include <sys/stat.h> /* for umask(), stat() */
+#include <unistd.h> /* for geteuid(), getpid(), stat() */
+#endif
+#include <assert.h>
+
+#include <libpq-fe.h>
+#include <pqexpbuffer.h>
+
+#include "stringutils.h"
+#include "mainloop.h"
+#include "copy.h"
+#include "help.h"
+#include "settings.h"
+#include "common.h"
+#include "large_obj.h"
+#include "print.h"
+#include "describe.h"
+#include "input.h"
+
+#ifdef WIN32
+#define popen(x,y) _popen(x,y)
+#define pclose(x) _pclose(x)
+#endif
+
+
+/* functions for use in this file only */
+
+static backslashResult
+exec_command(const char * cmd,
+ char * const * options,
+ const char * options_string,
+ PQExpBuffer query_buf,
+ PsqlSettings * pset);
+
+static bool
+do_edit(const char *filename_arg, PQExpBuffer query_buf);
+
+static char *
+unescape(const char * source, PsqlSettings * pset);
+
+static bool
+do_shell(const char *command);
+
+
+
+/*----------
+ * HandleSlashCmds:
+ *
+ * Handles all the different commands that start with '\',
+ * ordinarily called by MainLoop().
+ *
+ * 'line' is the current input line, which must start with a '\'
+ * (that is taken care of by MainLoop)
+ *
+ * 'query_buf' contains the query-so-far, which may be modified by
+ * execution of the backslash command (for example, \r clears it)
+ * query_buf can be NULL if there is no query-so-far.
+ *
+ * Returns a status code indicating what action is desired, see command.h.
+ *----------
+ */
+
+backslashResult
+HandleSlashCmds(PsqlSettings *pset,
+ const char *line,
+ PQExpBuffer query_buf,
+ const char ** end_of_cmd)
+{
+ backslashResult status = CMD_SKIP_LINE;
+ char * my_line;
+ char * options[17] ={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ char * token;
+ const char * options_string = NULL;
+ const char * cmd;
+ size_t blank_loc;
+ int i;
+ const char * continue_parse = NULL; /* tell the mainloop where the backslash command ended */
+
+ my_line = xstrdup(line);
+
+ /* Find the first whitespace (or backslash)
+ line[blank_loc] will now be the whitespace character
+ or the \0 at the end */
+ blank_loc = strcspn(my_line, " \t");
+
+ /* do we have an option string? */
+ if (my_line[blank_loc] != '\0') {
+ options_string = &my_line[blank_loc+1];
+
+ my_line[blank_loc] = '\0';
+ }
+
+ if (options_string) {
+ char quote;
+ unsigned int pos;
+ options_string = &options_string[strspn(options_string, " \t")]; /* skip leading whitespace */
+
+ i = 0;
+ token = strtokx(options_string, " \t", "\"'`", '\\', &quote, &pos);
+
+ for (i = 0; token && i<16; i++) {
+ switch(quote) {
+ case '"':
+ options[i] = unescape(token, pset);
+ break;
+ case '\'':
+ options[i] = xstrdup(token);
+ break;
+ case '`':
+ {
+ bool error = false;
+ FILE * fd = NULL;
+ char * file = unescape(token, pset);
+ PQExpBufferData output;
+ char buf[512];
+ size_t result;
+
+ fd = popen(file, "r");
+ if (!fd) {
+ perror(file);
+ error = true;
+ }
+
+ if (!error) {
+ initPQExpBuffer(&output);
+
+ do {
+ result = fread(buf, 1, 512, fd);
+ if (ferror(fd)) {
+ perror(file);
+ error = true;
+ break;
+ }
+ appendBinaryPQExpBuffer(&output, buf, result);
+ } while (!feof(fd));
+ appendPQExpBufferChar(&output, '\0');
+
+ if (pclose(fd) == -1) {
+ perror(file);
+ error = true;
+ }
+ }
+
+ if (!error) {
+ if (output.data[strlen(output.data)-1] == '\n')
+ output.data[strlen(output.data)-1] = '\0';
+ }
+
+ free(file);
+ if (!error)
+ options[i] = output.data;
+ else {
+ options[i] = xstrdup("");
+ termPQExpBuffer(&output);
+ }
+ break;
+ }
+ case 0:
+ default:
+ if (token[0] == '\\')
+ continue_parse = options_string + pos;
+ else if (token[0] == '$')
+ options[i] = xstrdup(interpolate_var(token+1, pset));
+ else
+ options[i] = xstrdup(token);
+ break;
+ }
+
+ if (continue_parse)
+ break;
+
+ token = strtokx(NULL, " \t", "\"'`", '\\', &quote, &pos);
+ }
+ }
+
+ cmd = my_line;
+
+ status = exec_command(cmd, options, options_string, query_buf, pset);
+
+ if (status == CMD_UNKNOWN) {
+ /* If the command was not recognized, try inserting a space after
+ the first letter and call again. The one letter commands
+ allow arguments to start immediately after the command,
+ but that is no longer encouraged. */
+ const char * new_options[17];
+ char new_cmd[2];
+ int i;
+
+ for (i=1; i<17; i++)
+ new_options[i] = options[i-1];
+ new_options[0] = cmd+1;
+
+ new_cmd[0] = cmd[0];
+ new_cmd[1] = '\0';
+
+ status = exec_command(new_cmd, (char * const *)new_options, my_line+2, query_buf, pset);
+ }
+
+ if (status == CMD_UNKNOWN) {
+ fprintf(stderr, "Unrecognized command: \\%s. Try \\? for help.\n", cmd);
+ status = CMD_ERROR;
+ }
+
+ if (continue_parse && *(continue_parse+1) == '\\')
+ continue_parse+=2;
+
+
+ if (end_of_cmd) {
+ if (continue_parse)
+ *end_of_cmd = line + (continue_parse - my_line);
+ else
+ *end_of_cmd = NULL;
+ }
+
+ /* clean up */
+ for (i = 0; i<16 && options[i]; i++)
+ free(options[i]);
+
+ free(my_line);
+
+ return status;
+}
+
+
+
+
+static backslashResult
+exec_command(const char * cmd,
+ char * const * options,
+ const char * options_string,
+ PQExpBuffer query_buf,
+ PsqlSettings * pset)
+{
+ bool success = true; /* indicate here if the command ran ok or failed */
+ bool quiet = GetVariableBool(pset->vars, "quiet");
+
+ backslashResult status = CMD_SKIP_LINE;
+
+
+ /* \a -- toggle field alignment
+ This is deprecated and makes no sense, but we keep it around. */
+ if (strcmp(cmd, "a") == 0) {
+ if (pset->popt.topt.format != PRINT_ALIGNED)
+ success = do_pset("format", "aligned", &pset->popt, quiet);
+ else
+ success = do_pset("format", "unaligned", &pset->popt, quiet);
+ }
+
+
+ /* \C -- override table title
+ (formerly change HTML caption) This is deprecated. */
+ else if (strcmp(cmd, "C") == 0)
+ success = do_pset("title", options[0], &pset->popt, quiet);
+
+
+
+ /* \c or \connect -- connect to new database or as different user
+ *
+ * \c foo bar : connect to db "foo" as user "bar"
+ * \c foo [-] : connect to db "foo" as current user
+ * \c - bar : connect to current db as user "bar"
+ * \c : connect to default db as default user
+ */
+ else if (strcmp(cmd, "c")==0 || strcmp(cmd, "connect")==0)
+ {
+ if (options[1])
+ /* gave username */
+ success = do_connect(options[0], options[1], pset);
+ else {
+ if (options[0])
+ /* gave database name */
+ success = do_connect(options[0], "", pset); /* empty string is same username as before,
+ NULL would mean libpq default */
+ else
+ /* connect to default db as default user */
+ success = do_connect(NULL, NULL, pset);
+ }
+ }
+
+
+ /* \copy */
+ else if (strcmp(cmd, "copy") == 0)
+ success = do_copy(options_string, pset);
+
+ /* \copyright */
+ else if (strcmp(cmd, "copyright") == 0)
+ print_copyright();
+
+ /* \d* commands */
+ else if (cmd[0] == 'd') {
+ switch(cmd[1]) {
+ case '\0':
+ if (options[0])
+ success = describeTableDetails(options[0], pset);
+ else
+ success = listTables("tvs", NULL, pset); /* standard listing of interesting things */
+ break;
+ case 'a':
+ success = describeAggregates(options[0], pset);
+ break;
+ case 'd':
+ success = objectDescription(options[0], pset);
+ break;
+ case 'f':
+ success = describeFunctions(options[0], pset);
+ break;
+ case 'l':
+ success = do_lo_list(pset);
+ break;
+ case 'o':
+ success = describeOperators(options[0], pset);
+ break;
+ case 'p':
+ success = permissionsList(options[0], pset);
+ break;
+ case 'T':
+ success = describeTypes(options[0], pset);
+ break;
+ case 't': case 'v': case 'i': case 's': case 'S':
+ if (cmd[1] == 'S' && cmd[2] == '\0')
+ success = listTables("Stvs", NULL, pset);
+ else
+ success = listTables(&cmd[1], options[0], pset);
+ break;
+ default:
+ status = CMD_UNKNOWN;
+ }
+ }
+
+
+ /* \e or \edit -- edit the current query buffer (or a file and make it the
+ query buffer */
+ else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
+ status = do_edit(options[0], query_buf) ? CMD_NEWEDIT : CMD_ERROR;
+
+
+ /* \echo */
+ else if (strcmp(cmd, "echo") == 0) {
+ int i;
+ for (i=0; i<16 && options[i]; i++)
+ fputs(options[i], stdout);
+ fputs("\n", stdout);
+ }
+
+ /* \f -- change field separator
+ (This is deprecated in favour of \pset.) */
+ else if (strcmp(cmd, "f") == 0)
+ success = do_pset("fieldsep", options[0], &pset->popt, quiet);
+
+
+ /* \g means send query */
+ else if (strcmp(cmd, "g") == 0) {
+ if (!options[0])
+ pset->gfname = NULL;
+ else
+ pset->gfname = xstrdup(options[0]);
+ status = CMD_SEND;
+ }
+
+ /* help */
+ else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
+ helpSQL(options_string);
+
+
+ /* HTML mode */
+ else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
+ success = do_pset("format", "html", &pset->popt, quiet);
+
+
+ /* \i is include file */
+ else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
+ {
+ if (!options[0]) {
+ fputs("Usage: \\i <filename>\n", stderr);
+ success = false;
+ }
+ else
+ success = process_file(options[0], pset);
+ }
+
+ /* \l is list databases */
+ else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0)
+ success = listAllDbs(pset);
+
+
+ /* large object things */
+ else if (strncmp(cmd, "lo_", 3)==0) {
+ if (strcmp(cmd+3, "export") == 0) {
+ if (!options[1]) {
+ fputs("Usage: \\lo_export <loid> <filename>\n", stderr);
+ success = false;
+ }
+ else
+ success = do_lo_export(pset, options[0], options[1]);
+ }
+
+ else if (strcmp(cmd+3, "import") == 0) {
+ if (!options[0]) {
+ fputs("Usage: \\lo_import <filename> [<description>]\n", stderr);
+ success = false;
+ }
+ else
+ success = do_lo_import(pset, options[0], options[1]);
+ }
+
+ else if (strcmp(cmd+3, "list") == 0)
+ success = do_lo_list(pset);
+
+ else if (strcmp(cmd+3, "unlink") == 0) {
+ if (!options[0]) {
+ fputs("Usage: \\lo_unlink <loid>\n", stderr);
+ success = false;
+ }
+ else
+ success = do_lo_unlink(pset, options[0]);
+ }
+
+ else
+ status = CMD_UNKNOWN;
+ }
+
+ /* \o -- set query output */
+ else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
+ success = setQFout(options[0], pset);
+
+
+ /* \p prints the current query buffer */
+ else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0 )
+ {
+ if (query_buf && query_buf->len > 0)
+ puts(query_buf->data);
+ else if (!GetVariableBool(pset->vars, "quiet"))
+ puts("Query buffer is empty.");
+ }
+
+ /* \pset -- set printing parameters */
+ else if (strcmp(cmd, "pset")==0) {
+ if (!options[0]) {
+ fputs("Usage: \\pset <parameter> [<value>]\n", stderr);
+ success = false;
+ }
+ else
+ success = do_pset(options[0], options[1], &pset->popt, quiet);
+ }
+
+ /* \q or \quit */
+ else if (strcmp(cmd, "q")==0 || strcmp(cmd, "quit")==0)
+ status = CMD_TERMINATE;
+
+ /* \qecho */
+ else if (strcmp(cmd, "qecho") == 0) {
+ int i;
+ for (i=0; i<16 && options[i]; i++)
+ fputs(options[i], pset->queryFout);
+ fputs("\n", pset->queryFout);
+ }
+
+ /* reset(clear) the buffer */
+ else if (strcmp(cmd, "r")==0 || strcmp(cmd, "reset")==0)
+ {
+ resetPQExpBuffer(query_buf);
+ if (!quiet) puts("Query buffer reset (cleared).");
+ }
+
+
+ /* \s save history in a file or show it on the screen */
+ else if (strcmp(cmd, "s")==0)
+ {
+ const char * fname;
+ if (!options[0])
+ fname = "/dev/tty";
+ else
+ fname = options[0];
+
+ success = saveHistory(fname);
+
+ if (success && !quiet && options[0])
+ printf("Wrote history to %s.\n", fname);
+ }
+
+
+ /* \set -- generalized set option command */
+ else if (strcmp(cmd, "set")==0)
+ {
+ if (!options[0]) {
+ /* list all variables */
+ /* (This is in utter violation of the GetVariable abstraction, but
+ I have not dreamt up a better way.) */
+ struct _variable * ptr;
+ for (ptr = pset->vars; ptr->next; ptr = ptr->next)
+ fprintf(stdout, "%s = '%s'\n", ptr->next->name, ptr->next->value);
+ success = true;
+ }
+ else {
+ if (!SetVariable(pset->vars, options[0], options[1])) {
+ fprintf(stderr, "Set variable failed.\n");
+ success = false;
+ }
+ }
+ }
+
+ /* \t -- turn off headers and row count */
+ else if (strcmp(cmd, "t")==0)
+ success = do_pset("tuples_only", NULL, &pset->popt, quiet);
+
+
+ /* \T -- define html <table ...> attributes */
+ else if (strcmp(cmd, "T")==0)
+ success = do_pset("tableattr", options[0], &pset->popt, quiet);
+
+
+ /* \w -- write query buffer to file */
+ else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0 )
+ {
+ FILE *fd = NULL;
+ bool pipe = false;
+
+ if (!options[0]) {
+ fprintf(stderr, "Usage \\%s <filename>\n", cmd);
+ success = false;
+ }
+ else {
+ if (options[0][0] == '|') {
+ pipe = true;
+#ifndef __CYGWIN32__
+ fd = popen(&options[0][1], "w");
+#else
+ fd = popen(&options[0][1], "wb");
+#endif
+ }
+ else {
+#ifndef __CYGWIN32__
+ fd = fopen(options[0], "w");
+#else
+ fd = fopen(options[0], "wb");
+#endif
+ }
+
+ if (!fd) {
+ perror(options[0]);
+ success = false;
+ }
+ }
+
+ if (fd) {
+ int result;
+
+ if (query_buf && query_buf->len > 0)
+ fprintf(fd, "%s\n", query_buf->data);
+
+ if (pipe)
+ result = pclose(fd);
+ else
+ result = fclose(fd);
+
+ if (result == EOF) {
+ perror("close");
+ success = false;
+ }
+ }
+ }
+
+ /* \x -- toggle expanded table representation */
+ else if (strcmp(cmd, "x")==0)
+ success = do_pset("expanded", NULL, &pset->popt, quiet);
+
+
+ /* list table rights (grant/revoke) */
+ else if (strcmp(cmd, "z")==0)
+ success = permissionsList(options[0], pset);
+
+
+ else if (strcmp(cmd, "!")==0)
+ success = do_shell(options_string);
+
+ else if (strcmp(cmd, "?")==0)
+ slashUsage(pset);
+
+
+#ifdef NOT_USED
+ /* These commands don't do anything. I just use them to test the parser. */
+ else if (strcmp(cmd, "void")==0 || strcmp(cmd, "#")==0)
+ {
+ int i;
+ fprintf(stderr, "+ optline = |%s|\n", options_string);
+ for(i=0; options[i]; i++)
+ fprintf(stderr, "+ opt%d = |%s|\n", i, options[i]);
+ }
+#endif
+
+ else {
+ status = CMD_UNKNOWN;
+ }
+
+ if (!success) status = CMD_ERROR;
+ return status;
+}
+
+
+
+/*
+ * unescape
+ *
+ * Replaces \n, \t, and the like.
+ * Also interpolates ${variables}.
+ *
+ * The return value is malloc()'ed.
+ */
+static char *
+unescape(const char * source, PsqlSettings * pset)
+{
+ unsigned char *p;
+ bool esc = false; /* Last character we saw was the
+ escape character */
+ char *destination, *tmp;
+ size_t length;
+
+#ifdef USE_ASSERT_CHECKING
+ assert(source);
+#endif
+
+ length = strlen(source)+1;
+
+ tmp = destination = (char *) malloc(length);
+ if (!tmp) {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+
+ for (p = (char *) source; *p; p += PQmblen(p)) {
+ if (esc) {
+ char c;
+
+ switch (*p)
+ {
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ {
+ long int l;
+ char * end;
+ l = strtol(p, &end, 0);
+ c = l;
+ p = end-1;
+ break;
+ }
+ default:
+ c = *p;
+ }
+ *tmp++ = c;
+ esc = false;
+ }
+
+ else if (*p == '\\') {
+ esc = true;
+ }
+
+ else if (*p == '$')
+ {
+ if (*(p+1) == '{') {
+ unsigned int len;
+ char *copy;
+ const char *value;
+ void * new;
+ len = strcspn(p+2, "}");
+ copy = xstrdup(p+2);
+ copy[len] = '\0';
+ value = interpolate_var(copy, pset);
+
+ length += strlen(value) - (len+3);
+ new = realloc(destination, length);
+ if (!new) {
+ perror("realloc");
+ exit(EXIT_FAILURE);
+ }
+ tmp = new + (tmp - destination);
+ destination = new;
+
+ strcpy(tmp, value);
+ tmp += strlen(value);
+ p += len + 2;
+ free(copy);
+ }
+ else {
+ *tmp++ = '$';
+ }
+ }
+
+ else {
+ *tmp++ = *p;
+ esc = false;
+ }
+ }
+
+ *tmp = '\0';
+ return destination;
+}
+
+
+
+
+/* do_connect
+ * -- handler for \connect
+ *
+ * Connects to a database (new_dbname) as a certain user (new_user).
+ * The new user can be NULL. A db name of "-" is the same as the old one.
+ * (That is, the one currently in pset. But pset->db can also be NULL. A NULL
+ * dbname is handled by libpq.)
+ * Returns true if all ok, false if the new connection couldn't be established
+ * but the old one was set back. Otherwise it terminates the program.
+ */
+bool
+do_connect(const char *new_dbname, const char *new_user, PsqlSettings * pset)
+{
+ PGconn *oldconn = pset->db;
+ const char *dbparam = NULL;
+ const char *userparam = NULL;
+ char *pwparam = NULL;
+ char * prompted_password = NULL;
+ char * prompted_user = NULL;
+ bool need_pass;
+ bool success = false;
+
+ /* If dbname is "-" then use old name, else new one (even if NULL) */
+ if (new_dbname && PQdb(oldconn) && (strcmp(new_dbname, "-") == 0 || strcmp(new_dbname, PQdb(oldconn))==0))
+ dbparam = PQdb(oldconn);
+ else
+ dbparam = new_dbname;
+
+ /* If user is "" or "-" then use the old one */
+ if ( new_user && PQuser(oldconn) && ( strcmp(new_user, "")==0 || strcmp(new_user, "-")==0 || strcmp(new_user, PQuser(oldconn))==0 )) {
+ userparam = PQuser(oldconn);
+ }
+ /* If username is "?" then prompt */
+ else if (new_user && strcmp(new_user, "?")==0)
+ userparam = prompted_user = simple_prompt("Username: ", 100, true); /* save for free() */
+ else
+ userparam = new_user;
+
+ /* need to prompt for password? */
+ if (pset->getPassword)
+ pwparam = prompted_password = simple_prompt("Password: ", 100, false); /* need to save for free() */
+
+ /* Use old password if no new one given (if you didn't have an old one, fine) */
+ if (!pwparam)
+ pwparam = PQpass(oldconn);
+
+
+#ifdef MULTIBYTE
+ /*
+ * PGCLIENTENCODING may be set by the previous connection. if a
+ * user does not explicitly set PGCLIENTENCODING, we should
+ * discard PGCLIENTENCODING so that libpq could get the backend
+ * encoding as the default PGCLIENTENCODING value. -- 1998/12/12
+ * Tatsuo Ishii
+ */
+
+ if (!pset->has_client_encoding)
+ putenv("PGCLIENTENCODING=");
+#endif
+
+ do {
+ need_pass = false;
+ pset->db = PQsetdbLogin(PQhost(oldconn), PQport(oldconn),
+ NULL, NULL, dbparam, userparam, pwparam);
+
+ if (PQstatus(pset->db)==CONNECTION_BAD &&
+ strcmp(PQerrorMessage(pset->db), "fe_sendauth: no password supplied\n")==0) {
+ need_pass = true;
+ free(prompted_password);
+ prompted_password = NULL;
+ pwparam = prompted_password = simple_prompt("Password: ", 100, false);
+ }
+ } while (need_pass);
+
+ free(prompted_password);
+ free(prompted_user);
+
+ /* If connection failed, try at least keep the old one.
+ That's probably more convenient than just kicking you out of the
+ program. */
+ if (!pset->db || PQstatus(pset->db) == CONNECTION_BAD)
+ {
+ fprintf(stderr, "Could not establish database connection.\n%s", PQerrorMessage(pset->db));
+ PQfinish(pset->db);
+ if (!oldconn || !pset->cur_cmd_interactive) { /* we don't want unpredictable things to happen
+ in scripting mode */
+ fputs("Terminating.\n", stderr);
+ if (oldconn)
+ PQfinish(oldconn);
+ pset->db = NULL;
+ }
+ else {
+ fputs("Keeping old connection.\n", stderr);
+ pset->db = oldconn;
+ }
+ }
+ else {
+ if (!GetVariable(pset->vars, "quiet")) {
+ if (userparam != new_user) /* no new user */
+ printf("You are now connected to database %s.\n", dbparam);
+ else if (dbparam != new_dbname) /* no new db */
+ printf("You are now connected as new user %s.\n", new_user);
+ else /* both new */
+ printf("You are now connected to database %s as user %s.\n",
+ PQdb(pset->db), PQuser(pset->db));
+ }
+
+ if (oldconn)
+ PQfinish(oldconn);
+
+ success = true;
+ }
+
+ return success;
+}
+
+
+
+/*
+ * do_edit -- handler for \e
+ *
+ * If you do not specify a filename, the current query buffer will be copied
+ * into a temporary one.
+ */
+
+static bool
+editFile(const char *fname)
+{
+ char *editorName;
+ char *sys;
+ int result;
+
+#ifdef USE_ASSERT_CHECKING
+ assert(fname);
+#else
+ if (!fname) return false;
+#endif
+
+ /* Find an editor to use */
+ editorName = getenv("PSQL_EDITOR");
+ if (!editorName)
+ editorName = getenv("EDITOR");
+ if (!editorName)
+ editorName = getenv("VISUAL");
+ if (!editorName)
+ editorName = DEFAULT_EDITOR;
+
+ sys = malloc(strlen(editorName) + strlen(fname) + 32 + 1);
+ if (!sys)
+ return false;
+ sprintf(sys, "exec %s %s", editorName, fname);
+ result = system(sys);
+ if (result == -1 || result == 127)
+ perror(sys);
+ free(sys);
+
+ return result==0;
+}
+
+
+/* call this one */
+static bool
+do_edit(const char *filename_arg, PQExpBuffer query_buf)
+{
+ char fnametmp[64];
+ FILE * stream;
+ const char *fname;
+ bool error = false;
+#ifndef WIN32
+ struct stat before, after;
+#endif
+
+#ifdef USE_ASSERT_CHECKING
+ assert(query_buf);
+#else
+ if (!query_buf) return false;
+#endif
+
+
+ if (filename_arg)
+ fname = filename_arg;
+
+ else {
+ /* make a temp file to edit */
+#ifndef WIN32
+ mode_t oldumask;
+
+ sprintf(fnametmp, "/tmp/psql.edit.%ld.%ld", (long) geteuid(), (long) getpid());
+#else
+ GetTempFileName(".", "psql", 0, fnametmp);
+#endif
+ fname = (const char *)fnametmp;
+
+#ifndef WIN32
+ oldumask = umask(0177);
+#endif
+ stream = fopen(fname, "w");
+#ifndef WIN32
+ umask(oldumask);
+#endif
+
+ if (!stream) {
+ perror(fname);
+ error = true;
+ }
+ else {
+ unsigned int ql = query_buf->len;
+ if (ql == 0 || query_buf->data[ql - 1] != '\n') {
+ appendPQExpBufferChar(query_buf, '\n');
+ ql++;
+ }
+
+ if (fwrite(query_buf->data, 1, ql, stream) != ql) {
+ perror(fname);
+ fclose(stream);
+ remove(fname);
+ error = true;
+ }
+ else
+ fclose(stream);
+ }
+ }
+
+#ifndef WIN32
+ if (!error && stat(fname, &before) != 0) {
+ perror(fname);
+ error = true;
+ }
+#endif
+
+ /* call editor */
+ if (!error)
+ error = !editFile(fname);
+
+#ifndef WIN32
+ if (!error && stat(fname, &after) !=0) {
+ perror(fname);
+ error = true;
+ }
+
+ if (!error && before.st_mtime != after.st_mtime) {
+#else
+ if (!error) {
+#endif
+ stream = fopen(fname, "r");
+ if (!stream) {
+ perror(fname);
+ error = true;
+ }
+ else {
+ /* read file back in */
+ char line[1024];
+ size_t result;
+
+ resetPQExpBuffer(query_buf);
+ do {
+ result = fread(line, 1, 1024, stream);
+ if (ferror(stream)) {
+ perror(fname);
+ error = true;
+ break;
+ }
+ appendBinaryPQExpBuffer(query_buf, line, result);
+ } while (!feof(stream));
+ appendPQExpBufferChar(query_buf, '\0');
+
+ fclose(stream);
+ }
+
+ /* remove temp file */
+ if (!filename_arg)
+ remove(fname);
+ }
+
+ return !error;
+}
+
+
+
+/*
+ * process_file
+ *
+ * Read commands from filename and then them to the main processing loop
+ * Handler for \i, but can be used for other things as well.
+ */
+bool
+process_file(const char *filename, PsqlSettings *pset)
+{
+ FILE *fd;
+ int result;
+
+ if (!filename)
+ return false;
+
+#ifdef __CYGWIN32__
+ fd = fopen(filename, "rb");
+#else
+ fd = fopen(filename, "r");
+#endif
+
+ if (!fd) {
+ perror(filename);
+ return false;
+ }
+
+ result = MainLoop(pset, fd);
+ fclose(fd);
+ return (result == EXIT_SUCCESS);
+}
+
+
+
+/*
+ * do_pset
+ *
+ */
+static const char *
+_align2string(enum printFormat in)
+{
+ switch (in) {
+ case PRINT_NOTHING:
+ return "nothing";
+ break;
+ case PRINT_UNALIGNED:
+ return "unaligned";
+ break;
+ case PRINT_ALIGNED:
+ return "aligned";
+ break;
+ case PRINT_HTML:
+ return "html";
+ break;
+ case PRINT_LATEX:
+ return "latex";
+ break;
+ }
+ return "unknown";
+}
+
+
+bool
+do_pset(const char * param, const char * value, printQueryOpt * popt, bool quiet)
+{
+ size_t vallen = 0;
+#ifdef USE_ASSERT_CHECKING
+ assert(param);
+#else
+ if (!param) return false;
+#endif
+
+ if (value)
+ vallen = strlen(value);
+
+ /* set format */
+ if (strcmp(param, "format")==0) {
+ if (!value)
+ ;
+ else if (strncasecmp("unaligned", value, vallen)==0)
+ popt->topt.format = PRINT_UNALIGNED;
+ else if (strncasecmp("aligned", value, vallen)==0)
+ popt->topt.format = PRINT_ALIGNED;
+ else if (strncasecmp("html", value, vallen)==0)
+ popt->topt.format = PRINT_HTML;
+ else if (strncasecmp("latex", value, vallen)==0)
+ popt->topt.format = PRINT_LATEX;
+ else {
+ fprintf(stderr, "Allowed formats are unaligned, aligned, html, latex.\n");
+ return false;
+ }
+
+ if (!quiet)
+ printf("Output format is %s.\n", _align2string(popt->topt.format));
+ }
+
+ /* set border style/width */
+ else if (strcmp(param, "border")==0) {
+ if (value)
+ popt->topt.border = atoi(value);
+
+ if (!quiet)
+ printf("Border style is %d.\n", popt->topt.border);
+ }
+
+ /* set expanded/vertical mode */
+ else if (strcmp(param, "x")==0 || strcmp(param, "expanded")==0 || strcmp(param, "vertical")==0) {
+ popt->topt.expanded = !popt->topt.expanded;
+ if (!quiet)
+ printf("Expanded display is %s.\n", popt->topt.expanded ? "on" : "off");
+ }
+
+ /* null display */
+ else if (strcmp(param, "null")==0) {
+ if (value) {
+ free(popt->nullPrint);
+ popt->nullPrint = xstrdup(value);
+ }
+ if (!quiet)
+ printf("Null display is \"%s\".\n", popt->nullPrint ? popt->nullPrint : "");
+ }
+
+ /* field separator for unaligned text */
+ else if (strcmp(param, "fieldsep")==0) {
+ if (value) {
+ free(popt->topt.fieldSep);
+ popt->topt.fieldSep = xstrdup(value);
+ }
+ if (!quiet)
+ printf("Field separator is \"%s\".\n", popt->topt.fieldSep);
+ }
+
+ /* toggle between full and barebones format */
+ else if (strcmp(param, "t")==0 || strcmp(param, "tuples_only")==0) {
+ popt->topt.tuples_only = !popt->topt.tuples_only;
+ if (!quiet) {
+ if (popt->topt.tuples_only)
+ puts("Showing only tuples.");
+ else
+ puts("Tuples only is off.");
+ }
+ }
+
+ /* set title override */
+ else if (strcmp(param, "title")==0) {
+ free(popt->title);
+ if (!value)
+ popt->title = NULL;
+ else
+ popt->title = xstrdup(value);
+
+ if (!quiet) {
+ if (popt->title)
+ printf("Title is \"%s\".\n", popt->title);
+ else
+ printf("Title is unset.\n");
+ }
+ }
+
+ /* set HTML table tag options */
+ else if (strcmp(param, "T")==0 || strcmp(param, "tableattr")==0) {
+ free(popt->topt.tableAttr);
+ if (!value)
+ popt->topt.tableAttr = NULL;
+ else
+ popt->topt.tableAttr = xstrdup(value);
+
+ if (!quiet) {
+ if (popt->topt.tableAttr)
+ printf("Table attribute is \"%s\".\n", popt->topt.tableAttr);
+ else
+ printf("Table attributes unset.\n");
+ }
+ }
+
+ /* toggle use of pager */
+ else if (strcmp(param, "pager")==0) {
+ popt->topt.pager = !popt->topt.pager;
+ if (!quiet) {
+ if (popt->topt.pager)
+ puts("Using pager is on.");
+ else
+ puts("Using pager is off.");
+ }
+ }
+
+
+ else {
+ fprintf(stderr, "Unknown option: %s\n", param);
+ return false;
+ }
+
+ return true;
+}
+
+
+
+#define DEFAULT_SHELL "/bin/sh"
+
+static bool
+do_shell(const char *command)
+{
+ int result;
+
+ if (!command) {
+ char *sys;
+ char *shellName;
+
+ shellName = getenv("SHELL");
+ if (shellName == NULL)
+ shellName = DEFAULT_SHELL;
+
+ sys = malloc(strlen(shellName) + 16);
+ if (!sys)
+ return false;
+ sprintf(sys, "exec %s", shellName);
+ result = system(sys);
+ free(sys);
+ }
+ else
+ result = system(command);
+
+ if (result==127 || result==-1) {
+ perror("system");
+ return false;
+ }
+ return true;
+}