postgresql/src/bin/psql/command.c
Bruce Momjian add932ee91 I'm continuing to work on cleaning up code in psql. As things appear
now, my changes seem to work.  Some possible minor bugs got squished
on the way but I can't be sure without more feedback from people who
really put the code to the test.

The new patch mostly simplifies variable handling and reduces code
duplication.  Changes in the command parser eliminate some redundant
variables (boolean state + depth counter), replaces some
"else if" constructs with switches, and so on.  It is meant to be
applied together with my previous patch, although I hope they don't
conflict; I went back to the CVS version for this one.

One more thing I thought should perhaps be changed: an IGNOREEOF
value of n will ignore only n-1 EOFs.  I didn't want to touch this
for fear of breaking existing applications, but it does seem a tad
illogical.

Jeroen T. Vermeulen
2003-03-20 06:43:35 +00:00

1961 lines
42 KiB
C

/*
* psql - the PostgreSQL interactive terminal
*
* Copyright 2000-2002 by PostgreSQL Global Development Group
*
* $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.94 2003/03/20 06:43:35 momjian Exp $
*/
#include "postgres_fe.h"
#include "command.h"
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifndef WIN32
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for stat() */
#include <fcntl.h> /* open() flags */
#include <unistd.h> /* for geteuid(), getpid(), stat() */
#else
#include <win32.h>
#include <io.h>
#include <fcntl.h>
#include <direct.h>
#endif
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "common.h"
#include "copy.h"
#include "describe.h"
#include "help.h"
#include "input.h"
#include "large_obj.h"
#include "mainloop.h"
#include "print.h"
#include "settings.h"
#include "variables.h"
#include "mb/pg_wchar.h"
/* functions for use in this file */
static backslashResult exec_command(const char *cmd,
const char *options_string,
const char **continue_parse,
PQExpBuffer query_buf,
volatile int *paren_level);
/* different ways for scan_option to handle parameter words */
enum option_type
{
OT_NORMAL, /* normal case */
OT_SQLID, /* treat as SQL identifier */
OT_SQLIDHACK, /* SQL identifier, but don't downcase */
OT_FILEPIPE /* it's a filename or pipe */
};
static char *scan_option(char **string, enum option_type type,
char *quote, bool semicolon);
static char *unescape(const unsigned char *source, size_t len);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf);
static bool do_connect(const char *new_dbname, const char *new_user);
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 should not start with a '\'
* but with the actual command name
* (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(const char *line,
PQExpBuffer query_buf,
const char **end_of_cmd,
volatile int *paren_level)
{
backslashResult status = CMD_SKIP_LINE;
char *my_line;
char *options_string = NULL;
size_t blank_loc;
const char *continue_parse = NULL; /* tell the mainloop where the
* backslash command ended */
#ifdef USE_ASSERT_CHECKING
assert(line);
#endif
my_line = xstrdup(line);
/*
* Find the first whitespace. line[blank_loc] will now be the
* whitespace character or the \0 at the end
*
* Also look for a backslash, so stuff like \p\g works.
*/
blank_loc = strcspn(my_line, " \t\n\r\\");
if (my_line[blank_loc] == '\\')
{
continue_parse = &my_line[blank_loc];
my_line[blank_loc] = '\0';
/* If it's a double backslash, we skip it. */
if (my_line[blank_loc + 1] == '\\')
continue_parse += 2;
}
/* do we have an option string? */
else if (my_line[blank_loc] != '\0')
{
options_string = &my_line[blank_loc + 1];
my_line[blank_loc] = '\0';
}
status = exec_command(my_line, options_string, &continue_parse, query_buf, paren_level);
if (status == CMD_UNKNOWN)
{
/*
* If the command was not recognized, try to parse it as a
* one-letter command with immediately following argument (a
* still-supported, but no longer encouraged, syntax).
*/
char new_cmd[2];
new_cmd[0] = my_line[0];
new_cmd[1] = '\0';
/* use line for options, because my_line was clobbered above */
status = exec_command(new_cmd, line + 1, &continue_parse, query_buf, paren_level);
/*
* continue_parse must be relative to my_line for calculation
* below
*/
continue_parse += my_line - line;
#if 0 /* turned out to be too annoying */
if (status != CMD_UNKNOWN && isalpha((unsigned char) new_cmd[0]))
psql_error("Warning: This syntax is deprecated.\n");
#endif
}
if (status == CMD_UNKNOWN)
{
if (pset.cur_cmd_interactive)
fprintf(stderr, gettext("Invalid command \\%s. Try \\? for help.\n"), my_line);
else
psql_error("invalid command \\%s\n", my_line);
status = CMD_ERROR;
}
if (continue_parse && *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 = line + strlen(line);
}
free(my_line);
return status;
}
static backslashResult
exec_command(const char *cmd,
const char *options_string,
const char **continue_parse,
PQExpBuffer query_buf,
volatile int *paren_level)
{
bool success = true; /* indicate here if the command ran ok or
* failed */
bool quiet = QUIET();
backslashResult status = CMD_SKIP_LINE;
char *string,
*string_cpy,
*val;
/*
* The 'string' variable will be overwritten to point to the next
* token, hence we need an extra pointer so we can free this at the
* end.
*/
if (options_string)
string = string_cpy = xstrdup(options_string);
else
string = string_cpy = NULL;
/*
* \a -- toggle field alignment This makes little 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) */
else if (strcmp(cmd, "C") == 0)
{
char *opt = scan_option(&string, OT_NORMAL, NULL, true);
success = do_pset("title", opt, &pset.popt, quiet);
free(opt);
}
/*----------
* \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)
{
char *opt1,
*opt2;
char opt1q,
opt2q;
/*
* Ideally we should treat the arguments as SQL identifiers. But
* for backwards compatibility with 7.2 and older pg_dump files,
* we have to take unquoted arguments verbatim (don't downcase
* them). For now, double-quoted arguments may be stripped of
* double quotes (as if SQL identifiers). By 7.4 or so, pg_dump
* files can be expected to double-quote all mixed-case \connect
* arguments, and then we can get rid of OT_SQLIDHACK.
*/
opt1 = scan_option(&string, OT_SQLIDHACK, &opt1q, true);
opt2 = scan_option(&string, OT_SQLIDHACK, &opt2q, true);
if (opt2)
/* gave username */
success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || strcmp(opt1, "") == 0) ? "" : opt1,
!opt2q && (strcmp(opt2, "-") == 0 || strcmp(opt2, "") == 0) ? "" : opt2);
else if (opt1)
/* gave database name */
success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || strcmp(opt1, "") == 0) ? "" : opt1, "");
else
/* connect to default db as default user */
success = do_connect(NULL, NULL);
free(opt1);
free(opt2);
}
/* \cd */
else if (strcmp(cmd, "cd") == 0)
{
char *opt = scan_option(&string, OT_NORMAL, NULL, true);
char *dir;
if (opt)
dir = opt;
else
{
#ifndef WIN32
struct passwd *pw;
pw = getpwuid(geteuid());
if (!pw)
{
psql_error("could not get home directory: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
dir = pw->pw_dir;
#else /* WIN32 */
/*
* On Windows, 'cd' without arguments prints the current
* directory, so if someone wants to code this here instead...
*/
dir = "/";
#endif /* WIN32 */
}
if (chdir(dir) == -1)
{
psql_error("\\%s: could not change directory to '%s': %s\n",
cmd, dir, strerror(errno));
success = false;
}
if (opt)
free(opt);
}
/* \copy */
else if (strcasecmp(cmd, "copy") == 0)
{
success = do_copy(options_string);
if (options_string)
string += strlen(string);
}
/* \copyright */
else if (strcmp(cmd, "copyright") == 0)
print_copyright();
/* \d* commands */
else if (cmd[0] == 'd')
{
char *pattern;
bool show_verbose;
/* We don't do SQLID reduction on the pattern yet */
pattern = scan_option(&string, OT_NORMAL, NULL, true);
show_verbose = strchr(cmd, '+') ? true : false;
switch (cmd[1])
{
case '\0':
case '+':
if (pattern)
success = describeTableDetails(pattern, show_verbose);
else
/* standard listing of interesting things */
success = listTables("tvs", NULL, show_verbose);
break;
case 'a':
success = describeAggregates(pattern, show_verbose);
break;
case 'c':
success = listConversions(pattern);
break;
case 'C':
success = listCasts(pattern);
break;
case 'd':
success = objectDescription(pattern);
break;
case 'D':
success = listDomains(pattern);
break;
case 'f':
success = describeFunctions(pattern, show_verbose);
break;
case 'l':
success = do_lo_list();
break;
case 'n':
success = listSchemas(pattern);
break;
case 'o':
success = describeOperators(pattern);
break;
case 'p':
success = permissionsList(pattern);
break;
case 'T':
success = describeTypes(pattern, show_verbose);
break;
case 't':
case 'v':
case 'i':
case 's':
case 'S':
success = listTables(&cmd[1], pattern, show_verbose);
break;
case 'u':
success = describeUsers(pattern);
break;
default:
status = CMD_UNKNOWN;
}
if (pattern)
free(pattern);
}
/*
* \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)
{
char *fname;
if (!query_buf)
{
psql_error("no query buffer\n");
status = CMD_ERROR;
}
else
{
fname = scan_option(&string, OT_NORMAL, NULL, true);
status = do_edit(fname, query_buf) ? CMD_NEWEDIT : CMD_ERROR;
free(fname);
}
}
/* \echo and \qecho */
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
{
char *value;
char quoted;
bool no_newline = false;
bool first = true;
FILE *fout;
if (strcmp(cmd, "qecho") == 0)
fout = pset.queryFout;
else
fout = stdout;
while ((value = scan_option(&string, OT_NORMAL, &quoted, false)))
{
if (!quoted && strcmp(value, "-n") == 0)
no_newline = true;
else
{
if (first)
first = false;
else
fputc(' ', fout);
fputs(value, fout);
}
free(value);
}
if (!no_newline)
fputs("\n", fout);
}
/* \encoding -- set/show client side encoding */
else if (strcmp(cmd, "encoding") == 0)
{
char *encoding = scan_option(&string, OT_NORMAL, NULL, false);
if (!encoding)
/* show encoding */
puts(pg_encoding_to_char(pset.encoding));
else
{
/* set encoding */
if (PQsetClientEncoding(pset.db, encoding) == -1)
psql_error("%s: invalid encoding name or conversion procedure not found\n", encoding);
else
{
/* save encoding info into psql internal data */
pset.encoding = PQclientEncoding(pset.db);
pset.popt.topt.encoding = PQclientEncoding(pset.db);
SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
}
free(encoding);
}
}
/* \f -- change field separator */
else if (strcmp(cmd, "f") == 0)
{
char *fname = scan_option(&string, OT_NORMAL, NULL, false);
success = do_pset("fieldsep", fname, &pset.popt, quiet);
free(fname);
}
/* \g means send query */
else if (strcmp(cmd, "g") == 0)
{
char *fname = scan_option(&string, OT_FILEPIPE, NULL, false);
if (!fname)
pset.gfname = NULL;
else
pset.gfname = xstrdup(fname);
free(fname);
status = CMD_SEND;
}
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
{
helpSQL(options_string ? &options_string[strspn(options_string, " \t\n\r")] : NULL,
pset.popt.topt.pager);
/* set pointer to end of line */
if (string)
string += strlen(string);
}
/* HTML mode */
else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
{
if (pset.popt.topt.format != PRINT_HTML)
success = do_pset("format", "html", &pset.popt, quiet);
else
success = do_pset("format", "aligned", &pset.popt, quiet);
}
/* \i is include file */
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
{
char *fname = scan_option(&string, OT_NORMAL, NULL, true);
if (!fname)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
{
success = (process_file(fname) == EXIT_SUCCESS);
free(fname);
}
}
/* \l is list databases */
else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0)
success = listAllDbs(false);
else if (strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
success = listAllDbs(true);
/*
* large object things
*/
else if (strncmp(cmd, "lo_", 3) == 0)
{
char *opt1,
*opt2;
opt1 = scan_option(&string, OT_NORMAL, NULL, true);
opt2 = scan_option(&string, OT_NORMAL, NULL, true);
if (strcmp(cmd + 3, "export") == 0)
{
if (!opt2)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
success = do_lo_export(opt1, opt2);
}
else if (strcmp(cmd + 3, "import") == 0)
{
if (!opt1)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
success = do_lo_import(opt1, opt2);
}
else if (strcmp(cmd + 3, "list") == 0)
success = do_lo_list();
else if (strcmp(cmd + 3, "unlink") == 0)
{
if (!opt1)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
success = do_lo_unlink(opt1);
}
else
status = CMD_UNKNOWN;
free(opt1);
free(opt2);
}
/* \o -- set query output */
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
{
char *fname = scan_option(&string, OT_FILEPIPE, NULL, true);
success = setQFout(fname);
free(fname);
}
/* \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 (!quiet)
puts(gettext("Query buffer is empty."));
fflush(stdout);
}
/* \pset -- set printing parameters */
else if (strcmp(cmd, "pset") == 0)
{
char *opt0 = scan_option(&string, OT_NORMAL, NULL, false);
char *opt1 = scan_option(&string, OT_NORMAL, NULL, false);
if (!opt0)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
success = do_pset(opt0, opt1, &pset.popt, quiet);
free(opt0);
free(opt1);
}
/* \q or \quit */
else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
status = CMD_TERMINATE;
/* reset(clear) the buffer */
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
{
resetPQExpBuffer(query_buf);
if (paren_level)
*paren_level = 0;
if (!quiet)
puts(gettext("Query buffer reset (cleared)."));
}
/* \s save history in a file or show it on the screen */
else if (strcmp(cmd, "s") == 0)
{
char *fname = scan_option(&string, OT_NORMAL, NULL, true);
success = saveHistory(fname ? fname : "/dev/tty");
if (success && !quiet && fname)
printf(gettext("Wrote history to %s.\n"), fname);
free(fname);
}
/* \set -- generalized set variable/option command */
else if (strcmp(cmd, "set") == 0)
{
char *opt0 = scan_option(&string, OT_NORMAL, NULL, false);
if (!opt0)
{
/* list all variables */
PrintVariables(pset.vars);
success = true;
}
else
{
/*
* Set variable to the concatenation of the arguments.
*/
char *newval = NULL;
char *opt;
opt = scan_option(&string, OT_NORMAL, NULL, false);
newval = xstrdup(opt ? opt : "");
free(opt);
while ((opt = scan_option(&string, OT_NORMAL, NULL, false)))
{
newval = realloc(newval, strlen(newval) + strlen(opt) + 1);
if (!newval)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
strcat(newval, opt);
free(opt);
}
if (!SetVariable(pset.vars, opt0, newval))
{
psql_error("\\%s: error\n", cmd);
success = false;
}
free(newval);
}
free(opt0);
}
/* \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)
{
char *value = scan_option(&string, OT_NORMAL, NULL, false);
success = do_pset("tableattr", value, &pset.popt, quiet);
free(value);
}
/* \timing -- toggle timing of queries */
else if (strcmp(cmd, "timing") == 0)
{
pset.timing = !pset.timing;
if (!quiet)
{
if (pset.timing)
puts(gettext(("Timing is on.")));
else
{
puts(gettext(("Timing is off.")));
}
}
}
/* \unset */
else if (strcmp(cmd, "unset") == 0)
{
char *opt = scan_option(&string, OT_NORMAL, NULL, false);
if (!opt)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else if (!SetVariable(pset.vars, opt, NULL))
{
psql_error("\\%s: error\n", cmd);
success = false;
}
free(opt);
}
/* \w -- write query buffer to file */
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
{
FILE *fd = NULL;
bool is_pipe = false;
char *fname = NULL;
if (!query_buf)
{
psql_error("no query buffer\n");
status = CMD_ERROR;
}
else
{
fname = scan_option(&string, OT_FILEPIPE, NULL, true);
if (!fname)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
{
if (fname[0] == '|')
{
is_pipe = true;
fd = popen(&fname[1], "w");
}
else
fd = fopen(fname, "w");
if (!fd)
{
psql_error("%s: %s\n", fname, strerror(errno));
success = false;
}
}
}
if (fd)
{
int result;
if (query_buf && query_buf->len > 0)
fprintf(fd, "%s\n", query_buf->data);
if (is_pipe)
result = pclose(fd);
else
result = fclose(fd);
if (result == EOF)
{
psql_error("%s: %s\n", fname, strerror(errno));
success = false;
}
}
free(fname);
}
/* \x -- toggle expanded table representation */
else if (strcmp(cmd, "x") == 0)
success = do_pset("expanded", NULL, &pset.popt, quiet);
/* \z -- list table rights (equivalent to \dp) */
else if (strcmp(cmd, "z") == 0)
{
char *pattern = scan_option(&string, OT_NORMAL, NULL, true);
success = permissionsList(pattern);
if (pattern)
free(pattern);
}
/* \! -- shell escape */
else if (strcmp(cmd, "!") == 0)
{
success = do_shell(options_string);
/* wind pointer to end of line */
if (string)
string += strlen(string);
}
/* \? -- slash command help */
else if (strcmp(cmd, "?") == 0)
slashUsage(pset.popt.topt.pager);
#if 0
/*
* 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 = 0;
char *value;
fprintf(stderr, "+ optstr = |%s|\n", options_string);
while ((value = scan_option(&string, OT_NORMAL, NULL, true)))
{
fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value);
free(value);
}
}
#endif
else
status = CMD_UNKNOWN;
if (!success)
status = CMD_ERROR;
/* eat the rest of the options string */
while ((val = scan_option(&string, OT_NORMAL, NULL, false)))
{
if (status != CMD_UNKNOWN)
psql_error("\\%s: extra argument '%s' ignored\n", cmd, val);
if (val)
free(val);
}
if (options_string && continue_parse)
*continue_parse = options_string + (string - string_cpy);
free(string_cpy);
return status;
}
/*
* scan_option()
*
* *string points to possible option string on entry; on exit, it's updated
* to point past the option string (if any).
*
* type tells what processing, if any, to perform on the option string;
* for example, if it's a SQL identifier, we want to downcase any unquoted
* letters.
*
* if quote is not NULL, *quote is set to 0 if no quoting was found, else
* the quote symbol.
*
* if semicolon is true, trailing semicolon(s) that would otherwise be taken
* as part of the option string will be stripped.
*
* Return value is NULL if no option found, else a malloc'd copy of the
* processed option value.
*/
static char *
scan_option(char **string, enum option_type type, char *quote, bool semicolon)
{
unsigned int pos;
char *options_string;
char *return_val;
if (quote)
*quote = 0;
if (!string || !(*string))
return NULL;
options_string = *string;
/* skip leading whitespace */
pos = strspn(options_string, " \t\n\r");
switch (options_string[pos])
{
/*
* End of line: no option present
*/
case '\0':
*string = &options_string[pos];
return NULL;
/*
* Next command: treat like end of line
*
* XXX this means we can't conveniently accept options that start
* with a backslash; therefore, option processing that
* encourages use of backslashes is rather broken.
*/
case '\\':
*string = &options_string[pos];
return NULL;
/*
* A single quote has a psql internal meaning, such as for
* delimiting file names, and it also allows for such escape
* sequences as \t.
*/
case '\'':
{
unsigned int jj;
unsigned short int bslash_count = 0;
for (jj = pos + 1; options_string[jj]; jj += PQmblen(&options_string[jj], pset.encoding))
{
if (options_string[jj] == '\'' && bslash_count % 2 == 0)
break;
if (options_string[jj] == '\\')
bslash_count++;
else
bslash_count = 0;
}
if (options_string[jj] == 0)
{
psql_error("parse error at the end of line\n");
*string = &options_string[jj];
return NULL;
}
return_val = unescape(&options_string[pos + 1], jj - pos - 1);
*string = &options_string[jj + 1];
if (quote)
*quote = '\'';
return return_val;
}
/*
* Backticks are for command substitution, like in shells
*/
case '`':
{
bool error = false;
FILE *fd;
char *file;
PQExpBufferData output;
char buf[512];
size_t result,
len;
len = strcspn(options_string + pos + 1, "`");
if (options_string[pos + 1 + len] == 0)
{
psql_error("parse error at the end of line\n");
*string = &options_string[pos + 1 + len];
return NULL;
}
options_string[pos + 1 + len] = '\0';
file = options_string + pos + 1;
fd = popen(file, "r");
if (!fd)
{
psql_error("%s: %s\n", file, strerror(errno));
error = true;
}
initPQExpBuffer(&output);
if (!error)
{
do
{
result = fread(buf, 1, 512, fd);
if (ferror(fd))
{
psql_error("%s: %s\n", file, strerror(errno));
error = true;
break;
}
appendBinaryPQExpBuffer(&output, buf, result);
} while (!feof(fd));
appendPQExpBufferChar(&output, '\0');
}
if (fd && pclose(fd) == -1)
{
psql_error("%s: %s\n", file, strerror(errno));
error = true;
}
if (!error)
{
if (output.data[strlen(output.data) - 1] == '\n')
output.data[strlen(output.data) - 1] = '\0';
return_val = output.data;
}
else
{
return_val = xstrdup("");
termPQExpBuffer(&output);
}
options_string[pos + 1 + len] = '`';
*string = options_string + pos + len + 2;
if (quote)
*quote = '`';
return return_val;
}
/*
* Variable substitution
*/
case ':':
{
size_t token_end;
const char *value;
char save_char;
token_end = strcspn(&options_string[pos + 1], " \t\n\r");
save_char = options_string[pos + token_end + 1];
options_string[pos + token_end + 1] = '\0';
value = GetVariable(pset.vars, options_string + pos + 1);
return_val = xstrdup(value ? value : "");
options_string[pos + token_end + 1] = save_char;
*string = &options_string[pos + token_end + 1];
/* XXX should we set *quote to ':' here? */
return return_val;
}
/*
* | could be the beginning of a pipe if so, take rest of line
* as command
*/
case '|':
if (type == OT_FILEPIPE)
{
*string += strlen(*string);
return xstrdup(options_string + pos);
}
/* fallthrough for other option types */
/*
* Default case: token extends to next whitespace, except that
* whitespace within double quotes doesn't end the token.
*
* If we are processing the option as a SQL identifier, then
* downcase unquoted letters and remove double-quotes --- but
* doubled double-quotes become output double-quotes, per
* spec.
*
* Note that a string like FOO"BAR"BAZ will be converted to
* fooBARbaz; this is somewhat inconsistent with the SQL spec,
* which would have us parse it as several identifiers. But
* for psql's purposes, we want a string like "foo"."bar" to
* be treated as one option, so there's little choice.
*/
default:
{
bool inquotes = false;
size_t token_len;
char *cp;
/* Find end of option */
cp = &options_string[pos];
for (;;)
{
/* Find next quote, whitespace, or end of string */
cp += strcspn(cp, "\" \t\n\r");
if (inquotes)
{
if (*cp == '\0')
{
psql_error("parse error at the end of line\n");
*string = cp;
return NULL;
}
if (*cp == '"')
inquotes = false;
cp++;
}
else
{
if (*cp != '"')
break; /* whitespace or end of string */
if (quote)
*quote = '"';
inquotes = true;
cp++;
}
}
*string = cp;
/* Copy the option */
token_len = cp - &options_string[pos];
return_val = malloc(token_len + 1);
if (!return_val)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
memcpy(return_val, &options_string[pos], token_len);
return_val[token_len] = '\0';
/* Strip any trailing semi-colons if requested */
if (semicolon)
{
int i;
for (i = token_len - 1;
i >= 0 && return_val[i] == ';';
i--)
/* skip */ ;
if (i < 0)
{
/* nothing left after stripping the semicolon... */
free(return_val);
return NULL;
}
if (i < (int) token_len - 1)
return_val[i + 1] = '\0';
}
/*
* If SQL identifier processing was requested, then we
* strip out excess double quotes and downcase unquoted
* letters.
*/
if (type == OT_SQLID || type == OT_SQLIDHACK)
{
inquotes = false;
cp = return_val;
while (*cp)
{
if (*cp == '"')
{
if (inquotes && cp[1] == '"')
{
/* Keep the first quote, remove the second */
cp++;
}
inquotes = !inquotes;
/* Collapse out quote at *cp */
memmove(cp, cp + 1, strlen(cp));
/* do not advance cp */
}
else
{
if (!inquotes && type == OT_SQLID)
{
if (isupper((unsigned char) *cp))
*cp = tolower((unsigned char) *cp);
}
cp += PQmblen(cp, pset.encoding);
}
}
}
return return_val;
}
}
}
/*
* unescape
*
* Replaces \n, \t, and the like.
*
* The return value is malloc()'ed.
*/
static char *
unescape(const unsigned char *source, size_t len)
{
const 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 = Min(len, strlen(source)) + 1;
tmp = destination = malloc(length);
if (!tmp)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
for (p = source; p - source < (int) len && *p; p += PQmblen(p, pset.encoding))
{
if (esc)
{
char c;
switch (*p)
{
case 'n':
c = '\n';
break;
case 't':
c = '\t';
break;
case 'b':
c = '\b';
break;
case 'r':
c = '\r';
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':
c = parse_char((char **)&p);
break;
default:
c = *p;
}
*tmp++ = c;
esc = false;
}
else if (*p == '\\')
esc = true;
else
{
int i;
const unsigned char *mp = p;
for (i = 0; i < PQmblen(p, pset.encoding); i++)
*tmp++ = *mp++;
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.
* The old connection will be kept if the session is interactive.
*/
static bool
do_connect(const char *new_dbname, const char *new_user)
{
PGconn *oldconn = pset.db;
const char *dbparam = NULL;
const char *userparam = NULL;
const char *pwparam = NULL;
char *prompted_password = NULL;
bool need_pass;
bool success = false;
/* Delete variables (in case we fail before setting them anew) */
SetVariable(pset.vars, "DBNAME", NULL);
SetVariable(pset.vars, "USER", NULL);
SetVariable(pset.vars, "HOST", NULL);
SetVariable(pset.vars, "PORT", NULL);
SetVariable(pset.vars, "ENCODING", NULL);
/* If dbname is "" then use old name, else new one (even if NULL) */
if (oldconn && new_dbname && PQdb(oldconn) && strcmp(new_dbname, "") == 0)
dbparam = PQdb(oldconn);
else
dbparam = new_dbname;
/* If user is "" then use the old one */
if (new_user && PQuser(oldconn) && strcmp(new_user, "") == 0)
userparam = PQuser(oldconn);
else
userparam = new_user;
/* need to prompt for password? */
if (pset.getPassword)
pwparam = prompted_password = simple_prompt("Password: ", 100, false);
/*
* Use old password (if any) if no new one given and we are
* reconnecting as same user
*/
if (!pwparam && oldconn && PQuser(oldconn) && userparam &&
strcmp(PQuser(oldconn), userparam) == 0)
pwparam = PQpass(oldconn);
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 &&
!feof(stdin))
{
PQfinish(pset.db);
need_pass = true;
free(prompted_password);
prompted_password = NULL;
pwparam = prompted_password = simple_prompt("Password: ", 100, false);
}
} while (need_pass);
free(prompted_password);
/*
* 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)
{
if (pset.cur_cmd_interactive)
{
psql_error("%s", PQerrorMessage(pset.db));
PQfinish(pset.db);
if (oldconn)
{
fputs(gettext("Previous connection kept\n"), stderr);
pset.db = oldconn;
}
else
pset.db = NULL;
}
else
{
/*
* we don't want unpredictable things to happen in scripting
* mode
*/
psql_error("\\connect: %s", PQerrorMessage(pset.db));
PQfinish(pset.db);
if (oldconn)
PQfinish(oldconn);
pset.db = NULL;
}
}
else
{
if (!QUIET())
{
if (userparam != new_user) /* no new user */
printf(gettext("You are now connected to database %s.\n"), dbparam);
else if (dbparam != new_dbname) /* no new db */
printf(gettext("You are now connected as new user %s.\n"), new_user);
else
/* both new */
printf(gettext("You are now connected to database %s as user %s.\n"),
PQdb(pset.db), PQuser(pset.db));
}
if (oldconn)
PQfinish(oldconn);
success = true;
}
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
pset.encoding = PQclientEncoding(pset.db);
pset.popt.topt.encoding = PQclientEncoding(pset.db);
/* Update variables */
SetVariable(pset.vars, "DBNAME", PQdb(pset.db));
SetVariable(pset.vars, "USER", PQuser(pset.db));
SetVariable(pset.vars, "HOST", PQhost(pset.db));
SetVariable(pset.vars, "PORT", PQport(pset.db));
SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
pset.issuper = test_superuser(PQuser(pset.db));
return success;
}
/*
* Test if the given user is a database superuser.
* (Is used to set up the prompt right.)
*/
bool
test_superuser(const char *username)
{
PGresult *res;
PQExpBufferData buf;
bool answer;
if (!username)
return false;
/*
* Use begin/commit to avoid starting a transaction block if server
* has autocommit off by default.
*/
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, "BEGIN; SELECT usesuper FROM pg_catalog.pg_user WHERE usename = '%s'; COMMIT", username);
res = PSQLexec(buf.data, true);
termPQExpBuffer(&buf);
answer =
(res && PQntuples(res) > 0 && PQnfields(res) > 0
&& !PQgetisnull(res, 0, 0)
&& PQgetvalue(res, 0, 0)
&& strcmp(PQgetvalue(res, 0, 0), "t") == 0);
PQclear(res);
return answer;
}
/*
* 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)
{
const 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) + 10 + 1);
if (!sys)
return false;
sprintf(sys, "exec %s '%s'", editorName, fname);
result = system(sys);
if (result == -1)
psql_error("could not start editor %s\n", editorName);
else if (result == 127)
psql_error("could not start /bin/sh\n");
free(sys);
return result == 0;
}
/* call this one */
static bool
do_edit(const char *filename_arg, PQExpBuffer query_buf)
{
char fnametmp[MAXPGPATH];
FILE *stream = NULL;
const char *fname;
bool error = false;
int fd;
#ifndef WIN32
struct stat before,
after;
#endif
if (filename_arg)
fname = filename_arg;
else
{
/* make a temp file to edit */
#ifndef WIN32
const char *tmpdirenv = getenv("TMPDIR");
snprintf(fnametmp, sizeof(fnametmp), "%s/psql.edit.%ld.%ld",
tmpdirenv ? tmpdirenv : "/tmp",
(long) geteuid(), (long) getpid());
#else
GetTempFileName(".", "psql", 0, fnametmp);
#endif
fname = (const char *) fnametmp;
fd = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd != -1)
stream = fdopen(fd, "w");
if (fd == -1 || !stream)
{
psql_error("could not open temporary file %s: %s\n", fname, strerror(errno));
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)
{
psql_error("%s: %s\n", fname, strerror(errno));
fclose(stream);
remove(fname);
error = true;
}
else
fclose(stream);
}
}
#ifndef WIN32
if (!error && stat(fname, &before) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
#endif
/* call editor */
if (!error)
error = !editFile(fname);
#ifndef WIN32
if (!error && stat(fname, &after) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
if (!error && before.st_mtime != after.st_mtime)
{
#else
if (!error)
{
#endif
stream = fopen(fname, "r");
if (!stream)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
else
{
/* read file back in */
char line[1024];
resetPQExpBuffer(query_buf);
while (fgets(line, sizeof(line), stream) != NULL)
appendPQExpBufferStr(query_buf, line);
if (ferror(stream))
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
#ifdef USE_READLINE
#ifdef HAVE_REPLACE_HISTORY_ENTRY
replace_history_entry(where_history(),query_buf->data,NULL);
#else
add_history(query_buf->data);
#endif
#endif
fclose(stream);
}
}
/* remove temp file */
if (!filename_arg)
{
if (remove(fname) == -1)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
}
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.
*/
int
process_file(char *filename)
{
FILE *fd;
int result;
char *oldfilename;
if (!filename)
return false;
fd = fopen(filename, "r");
if (!fd)
{
psql_error("%s: %s\n", filename, strerror(errno));
return false;
}
oldfilename = pset.inputfile;
pset.inputfile = filename;
result = MainLoop(fd);
fclose(fd);
pset.inputfile = oldfilename;
return result;
}
/*
* 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
{
psql_error("\\pset: allowed formats are unaligned, aligned, html, latex\n");
return false;
}
if (!quiet)
printf(gettext("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(gettext("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(popt->topt.expanded
? gettext("Expanded display is on.\n")
: gettext("Expanded display is off.\n"));
}
/* null display */
else if (strcmp(param, "null") == 0)
{
if (value)
{
free(popt->nullPrint);
popt->nullPrint = xstrdup(value);
}
if (!quiet)
printf(gettext("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(gettext("Field separator is '%s'.\n"), popt->topt.fieldSep);
}
/* record separator for unaligned text */
else if (strcmp(param, "recordsep") == 0)
{
if (value)
{
free(popt->topt.recordSep);
popt->topt.recordSep = xstrdup(value);
}
if (!quiet)
{
if (strcmp(popt->topt.recordSep, "\n") == 0)
printf(gettext("Record separator is <newline>."));
else
printf(gettext("Record separator is '%s'.\n"), popt->topt.recordSep);
}
}
/* 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(gettext("Showing only tuples."));
else
puts(gettext("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(gettext("Title is \"%s\".\n"), popt->title);
else
printf(gettext("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(gettext("Table attribute is \"%s\".\n"), popt->topt.tableAttr);
else
printf(gettext("Table attributes unset.\n"));
}
}
/* toggle use of pager */
else if (strcmp(param, "pager") == 0)
{
if (value && strcasecmp(value, "always") == 0)
popt->topt.pager = 2;
else if (popt->topt.pager == 1)
popt->topt.pager = 0;
else
popt->topt.pager = 1;
if (!quiet)
{
if (popt->topt.pager == 1)
puts(gettext("Pager is on (for long output)."));
else if (popt->topt.pager == 2)
puts(gettext("Pager is always (used)."));
else
puts(gettext("Pager is off."));
}
}
/* disable "(x rows)" footer */
else if (strcmp(param, "footer") == 0)
{
popt->default_footer = !popt->default_footer;
if (!quiet)
{
if (popt->default_footer)
puts(gettext("Default footer is on."));
else
puts(gettext("Default footer is off."));
}
}
else
{
psql_error("\\pset: 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;
const char *shellName;
shellName = getenv("SHELL");
if (shellName == NULL)
shellName = DEFAULT_SHELL;
sys = malloc(strlen(shellName) + 16);
if (!sys)
{
psql_error("out of memory\n");
if (pset.cur_cmd_interactive)
return false;
else
exit(EXIT_FAILURE);
}
sprintf(sys, "exec %s", shellName);
result = system(sys);
free(sys);
}
else
result = system(command);
if (result == 127 || result == -1)
{
psql_error("\\!: failed\n");
return false;
}
return true;
}