You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
postgres/src/bin/psql/command.c

1708 lines
36 KiB

/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2006, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.174.2.1 2010/03/09 01:09:54 momjian Exp $
*/
#include "postgres_fe.h"
#include "command.h"
#ifdef __BORLANDC__ /* needed for BCC */
#undef mkdir
#endif
#include <ctype.h>
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifndef WIN32
26 years ago
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for stat() */
#include <fcntl.h> /* open() flags */
26 years ago
#include <unistd.h> /* for geteuid(), getpid(), stat() */
#else
#include <win32.h>
Here is a patch to make the current snapshot compile on Win32 (native, libpq and psql) again. Changes are: 1) psql requires the includes of "io.h" and "fcntl.h" in command.c in order to make a call to open() work (io.h for _open(), fcntl.h for the O_xxx) 2) PG_VERSION is no longer defined in version.h[.in], but in configure.in. Since we don't do configure on native win32, we need to put it in config.h.win32 :-( 3) Added define of SYSCONFDIR to config.h.win32 - libpq won't compile without it. This functionality is *NOT* tested - it's just defined as "" for now. May work, may not. 4) DEF_PGPORT renamed to DEF_PGPORT_STR I have done the "basic tests" on it - it connects to a database, and I can run queries. Haven't tested any of the fancier functions (yet). However, I stepped on a much bigger problem when fixing psql to work. It no longer works when linked against the .DLL version of libpq (which the Makefile does for it). I have left it linked against this version anyway, pending the comments I get on this mail :-) The problem is that there are strings being allocated from libpq.dll using PQExpBuffers (for example, initPQExpBuffer() on line 92 of input.c). These are being allocated using the malloc function used by libpq.dll. This function *may* be different from the malloc function used by psql.exe - only the resulting pointer must be valid. And with the default linking methods, it *WILL* be different. Later, psql.exe tries to free() this string, at which point it crashes because the free() function can't find the allocated block (it's on the allocated blocks list used by the runtime lib of libpq.dll). Shouldn't the right thing to do be to have psql call termPQExpBuffer() on the data instead? As it is now, gets_fromFile() will just return the pointer received from the PQExpBuffer.data (this may well be present at several places - this is the one I was bitten by so far). Isn't that kind of "accessing the internals of the PQExpBuffer structure" wrong? Instead, perhaps it shuold make a copy of the string, adn then termPQExpBuffer() it? In that case, the string will have been allocated from within the same library as the free() is called. I can get it to work just fine by doing this - changing from (around line 100 of input.c): and the same a bit further down in the same function. But, as I said above, this may be at more places in the code? Perhaps someone more familiar to it could comment on that? What do you think shuld be done about this? Personally, I go by the "If you allocate a piece of memory using an interface, use the same interface to free it", but the question is how to make it work :-) Also, AFAIK this only affects psql.exe, so the changes made to the libpq this patch are required no matter how the other issue is handled. Regards, Magnus
25 years ago
#include <io.h>
#include <fcntl.h>
#include <direct.h>
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for stat() */
#endif
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "dumputils.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 "psqlscan.h"
#include "settings.h"
#include "variables.h"
#include "mb/pg_wchar.h"
/* functions for use in this file */
26 years ago
static backslashResult exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf);
static bool do_connect(char *dbname, char *user, char *host, char *port);
static bool do_shell(const char *command);
/*----------
* HandleSlashCmds:
*
* Handles all the different commands that start with '\',
* ordinarily called by MainLoop().
*
* scan_state is a lexer working state that is set to continue scanning
* just after the '\'. The lexer is advanced past the command and all
* arguments on return.
*
* '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(PsqlScanState scan_state,
PQExpBuffer query_buf)
{
backslashResult status = PSQL_CMD_SKIP_LINE;
char *cmd;
char *arg;
26 years ago
psql_assert(scan_state);
26 years ago
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
26 years ago
/* And try to execute it */
status = exec_command(cmd, scan_state, query_buf);
if (status == PSQL_CMD_UNKNOWN && strlen(cmd) > 1)
26 years ago
{
/*
* 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).
26 years ago
*/
char new_cmd[2];
26 years ago
/* don't change cmd until we know it's okay */
new_cmd[0] = cmd[0];
26 years ago
new_cmd[1] = '\0';
psql_scan_slash_pushback(scan_state, cmd + 1);
status = exec_command(new_cmd, scan_state, query_buf);
if (status != PSQL_CMD_UNKNOWN)
{
/* adjust cmd for possible messages below */
cmd[1] = '\0';
}
26 years ago
}
if (status == PSQL_CMD_UNKNOWN)
26 years ago
{
if (pset.cur_cmd_interactive)
fprintf(stderr, _("Invalid command \\%s. Try \\? for help.\n"), cmd);
else
psql_error("invalid command \\%s\n", cmd);
status = PSQL_CMD_ERROR;
26 years ago
}
if (status != PSQL_CMD_ERROR)
{
/* eat any remaining arguments after a valid command */
/* note we suppress evaluation of backticks here */
while ((arg = psql_scan_slash_option(scan_state,
OT_VERBATIM, NULL, false)))
{
psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
free(arg);
}
}
else
{
/* silently throw away rest of line after an erroneous command */
while ((arg = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false)))
free(arg);
}
/* if there is a trailing \\, swallow it */
psql_scan_slash_command_end(scan_state);
free(cmd);
26 years ago
return status;
}
/*
* Read and interpret an argument to the \connect slash command.
*/
static char *
read_connect_arg(PsqlScanState scan_state)
{
char *result;
char quote;
/*
* 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.
*/
result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
if (!result)
return NULL;
if (quote)
return result;
if (*result == '\0' || strcmp(result, "-") == 0)
return NULL;
return result;
}
/*
* Subroutine to actually try to execute a backslash command.
*/
static backslashResult
26 years ago
exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf)
{
26 years ago
bool success = true; /* indicate here if the command ran ok or
* failed */
backslashResult status = PSQL_CMD_SKIP_LINE;
/*
* \a -- toggle field alignment This makes little sense but we keep it
* around.
*/
26 years ago
if (strcmp(cmd, "a") == 0)
{
if (pset.popt.topt.format != PRINT_ALIGNED)
success = do_pset("format", "aligned", &pset.popt, pset.quiet);
26 years ago
else
success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
}
26 years ago
/* \C -- override table title (formerly change HTML caption) */
26 years ago
else if (strcmp(cmd, "C") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
success = do_pset("title", opt, &pset.popt, pset.quiet);
free(opt);
}
26 years ago
/*
* \c or \connect -- connect to database using the specified parameters.
*
* \c dbname user host port
*
* If any of these parameters are omitted or specified as '-', the current
* value of the parameter will be used instead. If the parameter has no
* current value, the default value for that parameter will be used. Some
* examples:
26 years ago
*
* \c - - hst Connect to current database on current port of host
* "hst" as current user. \c - usr - prt Connect to current database on
* "prt" port of current host as user "usr". \c dbs Connect to
* "dbs" database on current port of current host as current user.
26 years ago
*/
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
{
char *opt1,
*opt2,
*opt3,
*opt4;
opt1 = read_connect_arg(scan_state);
opt2 = read_connect_arg(scan_state);
opt3 = read_connect_arg(scan_state);
opt4 = read_connect_arg(scan_state);
success = do_connect(opt1, opt2, opt3, opt4);
free(opt1);
free(opt2);
free(opt3);
free(opt4);
}
/* \cd */
else if (strcmp(cmd, "cd") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
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 (pset.dirname)
pfree(pset.dirname);
pset.dirname = pg_strdup(dir);
canonicalize_path(pset.dirname);
if (opt)
free(opt);
}
26 years ago
/* \copy */
else if (pg_strcasecmp(cmd, "copy") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
success = do_copy(opt);
free(opt);
}
26 years ago
/* \copyright */
else if (strcmp(cmd, "copyright") == 0)
print_copyright();
26 years ago
/* \d* commands */
else if (cmd[0] == 'd')
{
char *pattern;
bool show_verbose;
/* We don't do SQLID reduction on the pattern yet */
pattern = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
show_verbose = strchr(cmd, '+') ? true : false;
26 years ago
switch (cmd[1])
{
case '\0':
case '+':
if (pattern)
success = describeTableDetails(pattern, show_verbose);
26 years ago
else
/* standard listing of interesting things */
success = listTables("tvs", NULL, show_verbose);
26 years ago
break;
case 'a':
success = describeAggregates(pattern, show_verbose);
26 years ago
break;
case 'b':
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
success = listConversions(pattern);
break;
case 'C':
success = listCasts(pattern);
break;
26 years ago
case 'd':
success = objectDescription(pattern);
26 years ago
break;
case 'D':
success = listDomains(pattern);
break;
26 years ago
case 'f':
success = describeFunctions(pattern, show_verbose);
26 years ago
break;
case 'g':
/* no longer distinct from \du */
success = describeRoles(pattern, show_verbose);
break;
26 years ago
case 'l':
success = do_lo_list();
26 years ago
break;
case 'n':
success = listSchemas(pattern, show_verbose);
break;
26 years ago
case 'o':
success = describeOperators(pattern);
26 years ago
break;
case 'p':
success = permissionsList(pattern);
26 years ago
break;
case 'T':
success = describeTypes(pattern, show_verbose);
26 years ago
break;
case 't':
case 'v':
case 'i':
case 's':
case 'S':
success = listTables(&cmd[1], pattern, show_verbose);
26 years ago
break;
case 'u':
success = describeRoles(pattern, show_verbose);
break;
26 years ago
default:
status = PSQL_CMD_UNKNOWN;
26 years ago
}
if (pattern)
free(pattern);
26 years ago
}
26 years ago
/*
* \e or \edit -- edit the current query buffer (or a file and make it the
* query buffer
26 years ago
*/
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
{
char *fname;
if (!query_buf)
{
psql_error("no query buffer\n");
status = PSQL_CMD_ERROR;
}
else
{
fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
expand_tilde(&fname);
if (fname)
canonicalize_path(fname);
status = do_edit(fname, query_buf) ? PSQL_CMD_NEWEDIT : PSQL_CMD_ERROR;
free(fname);
}
}
/* \echo and \qecho */
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
26 years ago
{
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 = psql_scan_slash_option(scan_state,
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);
26 years ago
}
/* \encoding -- set/show client side encoding */
else if (strcmp(cmd, "encoding") == 0)
{
char *encoding = psql_scan_slash_option(scan_state,
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 = pset.encoding;
SetVariable(pset.vars, "ENCODING",
pg_encoding_to_char(pset.encoding));
}
free(encoding);
}
}
/* \f -- change field separator */
26 years ago
else if (strcmp(cmd, "f") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
free(fname);
}
26 years ago
/* \g means send query */
else if (strcmp(cmd, "g") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, false);
if (!fname)
pset.gfname = NULL;
26 years ago
else
{
expand_tilde(&fname);
pset.gfname = pg_strdup(fname);
}
free(fname);
status = PSQL_CMD_SEND;
}
26 years ago
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
helpSQL(opt, pset.popt.topt.pager);
free(opt);
}
26 years ago
/* 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, pset.quiet);
else
success = do_pset("format", "aligned", &pset.popt, pset.quiet);
}
26 years ago
/* \i is include file */
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
if (!fname)
{
psql_error("\\%s: missing required argument\n", cmd);
26 years ago
success = false;
}
else
{
expand_tilde(&fname);
success = (process_file(fname, false) == EXIT_SUCCESS);
free(fname);
}
}
26 years ago
/* \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);
26 years ago
/*
* large object things
*/
26 years ago
else if (strncmp(cmd, "lo_", 3) == 0)
{
char *opt1,
*opt2;
opt1 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
opt2 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
26 years ago
if (strcmp(cmd + 3, "export") == 0)
{
if (!opt2)
26 years ago
{
psql_error("\\%s: missing required argument\n", cmd);
26 years ago
success = false;
}
else
{
expand_tilde(&opt2);
success = do_lo_export(opt1, opt2);
}
26 years ago
}
else if (strcmp(cmd + 3, "import") == 0)
{
if (!opt1)
26 years ago
{
psql_error("\\%s: missing required argument\n", cmd);
26 years ago
success = false;
}
else
{
expand_tilde(&opt1);
success = do_lo_import(opt1, opt2);
}
26 years ago
}
else if (strcmp(cmd + 3, "list") == 0)
success = do_lo_list();
26 years ago
else if (strcmp(cmd + 3, "unlink") == 0)
{
if (!opt1)
26 years ago
{
psql_error("\\%s: missing required argument\n", cmd);
26 years ago
success = false;
}
else
success = do_lo_unlink(opt1);
26 years ago
}
else
status = PSQL_CMD_UNKNOWN;
free(opt1);
free(opt2);
}
26 years ago
/* \o -- set query output */
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, true);
expand_tilde(&fname);
success = setQFout(fname);
free(fname);
}
26 years ago
/* \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 (!pset.quiet)
puts(_("Query buffer is empty."));
fflush(stdout);
}
/* \password -- set user password */
else if (strcmp(cmd, "password") == 0)
{
char *pw1;
char *pw2;
pw1 = simple_prompt("Enter new password: ", 100, false);
pw2 = simple_prompt("Enter it again: ", 100, false);
if (strcmp(pw1, pw2) != 0)
{
fprintf(stderr, _("Passwords didn't match.\n"));
success = false;
}
else
{
char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true);
char *user;
char *encrypted_password;
if (opt0)
user = opt0;
else
user = PQuser(pset.db);
encrypted_password = PQencryptPassword(pw1, user);
if (!encrypted_password)
{
fprintf(stderr, _("Password encryption failed.\n"));
success = false;
}
else
{
PQExpBufferData buf;
PGresult *res;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
fmtId(user));
appendStringLiteralConn(&buf, encrypted_password, pset.db);
res = PSQLexec(buf.data, false);
termPQExpBuffer(&buf);
if (!res)
success = false;
else
PQclear(res);
PQfreemem(encrypted_password);
}
}
free(pw1);
free(pw2);
}
26 years ago
/* \pset -- set printing parameters */
else if (strcmp(cmd, "pset") == 0)
{
char *opt0 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
char *opt1 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!opt0)
26 years ago
{
psql_error("\\%s: missing required argument\n", cmd);
26 years ago
success = false;
}
else
success = do_pset(opt0, opt1, &pset.popt, pset.quiet);
free(opt0);
free(opt1);
}
26 years ago
/* \q or \quit */
else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
status = PSQL_CMD_TERMINATE;
26 years ago
/* reset(clear) the buffer */
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
{
resetPQExpBuffer(query_buf);
psql_scan_reset(scan_state);
if (!pset.quiet)
puts(_("Query buffer reset (cleared)."));
}
26 years ago
/* \s save history in a file or show it on the screen */
else if (strcmp(cmd, "s") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
26 years ago
expand_tilde(&fname);
/* This scrolls off the screen when using /dev/tty */
success = saveHistory(fname ? fname : DEVTTY, false);
if (success && !pset.quiet && fname)
printf(gettext("Wrote history to file \"%s/%s\".\n"),
pset.dirname ? pset.dirname : ".", fname);
if (!fname)
putchar('\n');
free(fname);
26 years ago
}
/* \set -- generalized set variable/option command */
26 years ago
else if (strcmp(cmd, "set") == 0)
{
char *opt0 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!opt0)
26 years ago
{
/* list all variables */
PrintVariables(pset.vars);
26 years ago
success = true;
}
else
{
/*
* Set variable to the concatenation of the arguments.
*/
char *newval;
char *opt;
opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
newval = pg_strdup(opt ? opt : "");
free(opt);
while ((opt = psql_scan_slash_option(scan_state,
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))
26 years ago
{
psql_error("\\%s: error\n", cmd);
26 years ago
success = false;
}
free(newval);
26 years ago
}
free(opt0);
}
26 years ago
/* \t -- turn off headers and row count */
else if (strcmp(cmd, "t") == 0)
success = do_pset("tuples_only", NULL, &pset.popt, pset.quiet);
26 years ago
/* \T -- define html <table ...> attributes */
else if (strcmp(cmd, "T") == 0)
{
char *value = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
success = do_pset("tableattr", value, &pset.popt, pset.quiet);
free(value);
}
/* \timing -- toggle timing of queries */
else if (strcmp(cmd, "timing") == 0)
{
pset.timing = !pset.timing;
if (!pset.quiet)
{
if (pset.timing)
puts(_("Timing is on."));
else
puts(_("Timing is off."));
}
}
23 years ago
/* \unset */
else if (strcmp(cmd, "unset") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
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);
}
26 years ago
/* \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 = PSQL_CMD_ERROR;
}
else
{
fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, true);
expand_tilde(&fname);
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
{
canonicalize_path(fname);
fd = fopen(fname, "w");
}
if (!fd)
{
psql_error("%s: %s\n", fname, strerror(errno));
success = false;
}
}
}
26 years ago
if (fd)
{
int result;
26 years ago
if (query_buf && query_buf->len > 0)
fprintf(fd, "%s\n", query_buf->data);
if (is_pipe)
26 years ago
result = pclose(fd);
else
result = fclose(fd);
26 years ago
if (result == EOF)
{
psql_error("%s: %s\n", fname, strerror(errno));
26 years ago
success = false;
}
}
free(fname);
}
26 years ago
/* \x -- toggle expanded table representation */
else if (strcmp(cmd, "x") == 0)
success = do_pset("expanded", NULL, &pset.popt, pset.quiet);
/* \z -- list table rights (equivalent to \dp) */
26 years ago
else if (strcmp(cmd, "z") == 0)
{
char *pattern = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
success = permissionsList(pattern);
if (pattern)
free(pattern);
}
/* \! -- shell escape */
26 years ago
else if (strcmp(cmd, "!") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
success = do_shell(opt);
free(opt);
}
26 years ago
/* \? -- slash command help */
26 years ago
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.
26 years ago
*/
else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0)
{
int i = 0;
char *value;
26 years ago
while ((value = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true)))
{
fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value);
free(value);
}
26 years ago
}
#endif
26 years ago
else
status = PSQL_CMD_UNKNOWN;
26 years ago
if (!success)
status = PSQL_CMD_ERROR;
26 years ago
return status;
}
/*
* Ask the user for a password; 'username' is the username the
* password is for, if one has been explicitly specified. Returns a
* malloc'd string.
*/
static char *
prompt_for_password(const char *username)
{
char *result;
26 years ago
if (username == NULL)
result = simple_prompt("Password: ", 100, false);
26 years ago
else
{
char *prompt_text;
26 years ago
prompt_text = malloc(strlen(username) + 32);
sprintf(prompt_text, "Password for user \"%s\": ", username);
result = simple_prompt(prompt_text, 100, false);
free(prompt_text);
}
return result;
}
static bool
param_is_newly_set(const char *old_val, const char *new_val)
{
if (new_val == NULL)
return false;
if (old_val == NULL || strcmp(old_val, new_val) != 0)
return true;
return false;
}
/*
* do_connect -- handler for \connect
*
* Connects to a database with given parameters. If there exists an
* established connection, NULL values will be replaced with the ones
* in the current connection. Otherwise NULL will be passed for that
* parameter to PQsetdbLogin(), so the libpq defaults will be used.
*
* In interactive mode, if connection fails with the given parameters,
* the old connection will be kept.
*/
static bool
do_connect(char *dbname, char *user, char *host, char *port)
{
PGconn *o_conn = pset.db,
*n_conn;
char *password = NULL;
if (!dbname)
dbname = PQdb(o_conn);
if (!user)
user = PQuser(o_conn);
if (!host)
host = PQhost(o_conn);
if (!port)
port = PQport(o_conn);
26 years ago
/*
* If the user asked to be prompted for a password, ask for one now. If
* not, use the password from the old connection, provided the username
* has not changed. Otherwise, try to connect without a password first,
* and then ask for a password if we got the appropriate error message.
*
* XXX: this behavior is broken. It leads to spurious connection attempts
* in the postmaster's log, and doing a string comparison against the
* returned error message is pretty fragile.
26 years ago
*/
if (pset.getPassword)
{
password = prompt_for_password(user);
}
else if (o_conn && user && strcmp(PQuser(o_conn), user) == 0)
{
password = strdup(PQpass(o_conn));
}
while (true)
26 years ago
{
n_conn = PQsetdbLogin(host, port, NULL, NULL,
dbname, user, password);
/* We can immediately discard the password -- no longer needed */
if (password)
free(password);
26 years ago
if (PQstatus(n_conn) == CONNECTION_OK)
break;
/*
* Connection attempt failed; either retry the connection attempt with
* a new password, or give up.
*/
if (strcmp(PQerrorMessage(n_conn), PQnoPasswordSupplied) == 0)
26 years ago
{
PQfinish(n_conn);
password = prompt_for_password(user);
continue;
26 years ago
}
/*
* Failed to connect to the database. In interactive mode, keep the
* previous connection to the DB; in scripting mode, close our
* previous connection as well.
*/
if (pset.cur_cmd_interactive)
{
psql_error("%s", PQerrorMessage(n_conn));
/* pset.db is left unmodified */
if (o_conn)
fputs(_("Previous connection kept\n"), stderr);
}
else
{
psql_error("\\connect: %s", PQerrorMessage(n_conn));
if (o_conn)
{
PQfinish(o_conn);
pset.db = NULL;
}
26 years ago
}
PQfinish(n_conn);
return false;
26 years ago
}
/*
* Replace the old connection with the new one, and update
* connection-dependent variables.
*/
PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL);
pset.db = n_conn;
SyncVariables();
/* Tell the user about the new connection */
if (!pset.quiet)
{
printf(_("You are now connected to database \"%s\""), PQdb(pset.db));
if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db)))
printf(_(" on host \"%s\""), PQhost(pset.db));
if (param_is_newly_set(PQport(o_conn), PQport(pset.db)))
printf(_(" at port \"%s\""), PQport(pset.db));
if (param_is_newly_set(PQuser(o_conn), PQuser(pset.db)))
printf(_(" as user \"%s\""), PQuser(pset.db));
printf(".\n");
}
if (o_conn)
PQfinish(o_conn);
return true;
}
/*
* SyncVariables
*
* Make psql's internal variables agree with connection state upon
* establishing a new connection.
*/
void
SyncVariables(void)
{
/* get stuff from connection */
pset.encoding = PQclientEncoding(pset.db);
pset.popt.topt.encoding = pset.encoding;
pset.sversion = PQserverVersion(pset.db);
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));
/* send stuff to it, too */
PQsetErrorVerbosity(pset.db, pset.verbosity);
}
/*
* UnsyncVariables
*
* Clear variables that should be not be set when there is no connection.
*/
void
UnsyncVariables(void)
{
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);
}
/*
* 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;
26 years ago
char *sys;
int result;
psql_assert(fname);
26 years ago
/* Find an editor to use */
editorName = getenv("PSQL_EDITOR");
if (!editorName)
editorName = getenv("EDITOR");
if (!editorName)
editorName = getenv("VISUAL");
if (!editorName)
editorName = DEFAULT_EDITOR;
/*
* On Unix the EDITOR value should *not* be quoted, since it might include
* switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
* if necessary. But this policy is not very workable on Windows, due to
* severe brain damage in their command shell plus the fact that standard
* program paths include spaces.
*/
sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
#ifndef WIN32
sprintf(sys, "exec %s '%s'", editorName, fname);
#else
sprintf(sys, "%s\"%s\" \"%s\"%s",
SYSTEMQUOTE, editorName, fname, SYSTEMQUOTE);
#endif
26 years ago
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");
26 years ago
free(sys);
return result == 0;
}
/* call this one */
static bool
do_edit(const char *filename_arg, PQExpBuffer query_buf)
{
char fnametmp[MAXPGPATH];
25 years ago
FILE *stream = NULL;
26 years ago
const char *fname;
bool error = false;
int fd;
26 years ago
struct stat before,
after;
26 years ago
if (filename_arg)
fname = filename_arg;
else
{
/* make a temp file to edit */
#ifndef WIN32
const char *tmpdir = getenv("TMPDIR");
if (!tmpdir)
tmpdir = "/tmp";
#else
char tmpdir[MAXPGPATH];
int ret;
ret = GetTempPath(MAXPGPATH, tmpdir);
if (ret == 0 || ret > MAXPGPATH)
{
psql_error("cannot locate temporary directory: %s",
!ret ? strerror(errno) : "");
return false;
}
/*
* No canonicalize_path() here. EDIT.EXE run from CMD.EXE prepends the
* current directory to the supplied path unless we use only
* backslashes, so we do that.
*/
#endif
#ifndef WIN32
snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d", tmpdir,
"/", (int) getpid());
#else
snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d", tmpdir,
"" /* trailing separator already present */ , (int) getpid());
#endif
26 years ago
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)
26 years ago
{
psql_error("could not open temporary file \"%s\": %s\n", fname, strerror(errno));
26 years ago
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));
26 years ago
fclose(stream);
remove(fname);
error = true;
}
else if (fclose(stream) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
remove(fname);
error = true;
}
26 years ago
}
}
26 years ago
if (!error && stat(fname, &before) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
26 years ago
/* call editor */
if (!error)
error = !editFile(fname);
26 years ago
if (!error && stat(fname, &after) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
26 years ago
error = true;
}
26 years ago
if (!error && before.st_mtime != after.st_mtime)
{
stream = fopen(fname, PG_BINARY_R);
26 years ago
if (!stream)
{
psql_error("%s: %s\n", fname, strerror(errno));
26 years ago
error = true;
}
else
{
/* read file back into query_buf */
26 years ago
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;
}
26 years ago
fclose(stream);
}
}
/* remove temp file */
if (!filename_arg)
{
if (remove(fname) == -1)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
}
26 years ago
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. Returns
* MainLoop() error code.
*/
int
process_file(char *filename, bool single_txn)
{
26 years ago
FILE *fd;
int result;
char *oldfilename;
PGresult *res;
26 years ago
if (!filename)
return EXIT_FAILURE;
canonicalize_path(filename);
fd = fopen(filename, PG_BINARY_R);
26 years ago
if (!fd)
{
psql_error("%s: %s\n", filename, strerror(errno));
return EXIT_FAILURE;
26 years ago
}
oldfilename = pset.inputfile;
pset.inputfile = filename;
if (single_txn)
{
if ((res = PSQLexec("BEGIN", false)) == NULL)
{
if (pset.on_error_stop)
return EXIT_USER;
}
else
PQclear(res);
}
result = MainLoop(fd);
if (single_txn)
{
if ((res = PSQLexec("COMMIT", false)) == NULL)
{
if (pset.on_error_stop)
return EXIT_USER;
}
else
PQclear(res);
}
26 years ago
fclose(fd);
pset.inputfile = oldfilename;
return result;
}
/*
* do_pset
*
*/
static const char *
_align2string(enum printFormat in)
{
26 years ago
switch (in)
{
case PRINT_NOTHING:
26 years ago
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;
case PRINT_TROFF_MS:
return "troff-ms";
break;
26 years ago
}
return "unknown";
}
bool
do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
{
26 years ago
size_t vallen = 0;
psql_assert(param);
26 years ago
if (value)
vallen = strlen(value);
/* set format */
if (strcmp(param, "format") == 0)
{
if (!value)
;
else if (pg_strncasecmp("unaligned", value, vallen) == 0)
26 years ago
popt->topt.format = PRINT_UNALIGNED;
else if (pg_strncasecmp("aligned", value, vallen) == 0)
26 years ago
popt->topt.format = PRINT_ALIGNED;
else if (pg_strncasecmp("html", value, vallen) == 0)
26 years ago
popt->topt.format = PRINT_HTML;
else if (pg_strncasecmp("latex", value, vallen) == 0)
26 years ago
popt->topt.format = PRINT_LATEX;
else if (pg_strncasecmp("troff-ms", value, vallen) == 0)
popt->topt.format = PRINT_TROFF_MS;
26 years ago
else
{
psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
26 years ago
return false;
}
if (!quiet)
printf(_("Output format is %s.\n"), _align2string(popt->topt.format));
}
26 years ago
/* set border style/width */
else if (strcmp(param, "border") == 0)
{
if (value)
popt->topt.border = atoi(value);
26 years ago
if (!quiet)
printf(_("Border style is %d.\n"), popt->topt.border);
}
26 years ago
/* 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
? _("Expanded display is on.\n")
: _("Expanded display is off.\n"));
}
/* locale-aware numeric output */
else if (strcmp(param, "numericlocale") == 0)
{
popt->topt.numericLocale = !popt->topt.numericLocale;
if (!quiet)
{
if (popt->topt.numericLocale)
puts(_("Showing locale-adjusted numeric output."));
else
puts(_("Locale-adjusted numeric output is off."));
}
}
26 years ago
/* null display */
else if (strcmp(param, "null") == 0)
{
if (value)
{
free(popt->nullPrint);
popt->nullPrint = pg_strdup(value);
26 years ago
}
if (!quiet)
printf(_("Null display is \"%s\".\n"), popt->nullPrint ? popt->nullPrint : "");
}
26 years ago
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
if (value)
{
free(popt->topt.fieldSep);
popt->topt.fieldSep = pg_strdup(value);
26 years ago
}
if (!quiet)
printf(_("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 = pg_strdup(value);
}
if (!quiet)
{
if (strcmp(popt->topt.recordSep, "\n") == 0)
printf(_("Record separator is <newline>."));
else
printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep);
}
26 years ago
}
/* toggle between full and tuples-only format */
26 years ago
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."));
26 years ago
else
puts(_("Tuples only is off."));
26 years ago
}
}
/* set title override */
else if (strcmp(param, "title") == 0)
{
free(popt->title);
if (!value)
popt->title = NULL;
else
popt->title = pg_strdup(value);
26 years ago
if (!quiet)
{
if (popt->title)
printf(_("Title is \"%s\".\n"), popt->title);
26 years ago
else
printf(_("Title is unset.\n"));
26 years ago
}
}
26 years ago
/* 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 = pg_strdup(value);
26 years ago
if (!quiet)
{
if (popt->topt.tableAttr)
printf(_("Table attribute is \"%s\".\n"), popt->topt.tableAttr);
26 years ago
else
printf(_("Table attributes unset.\n"));
26 years ago
}
}
26 years ago
/* toggle use of pager */
else if (strcmp(param, "pager") == 0)
{
if (value && pg_strcasecmp(value, "always") == 0)
22 years ago
popt->topt.pager = 2;
else if (popt->topt.pager == 1)
22 years ago
popt->topt.pager = 0;
else
22 years ago
popt->topt.pager = 1;
26 years ago
if (!quiet)
{
if (popt->topt.pager == 1)
puts(_("Pager is used for long output."));
else if (popt->topt.pager == 2)
puts(_("Pager is always used."));
26 years ago
else
puts(_("Pager usage is off."));
26 years ago
}
}
/* disable "(x rows)" footer */
else if (strcmp(param, "footer") == 0)
{
popt->default_footer = !popt->default_footer;
if (!quiet)
{
if (popt->default_footer)
puts(_("Default footer is on."));
else
puts(_("Default footer is off."));
}
}
26 years ago
else
{
psql_error("\\pset: unknown option: %s\n", param);
26 years ago
return false;
}
return true;
}
#ifndef WIN32
#define DEFAULT_SHELL "/bin/sh"
#else
/*
* CMD.EXE is in different places in different Win32 releases so we
* have to rely on the path to find it.
*/
#define DEFAULT_SHELL "cmd.exe"
#endif
static bool
do_shell(const char *command)
{
26 years ago
int result;
26 years ago
if (!command)
{
char *sys;
const char *shellName;
26 years ago
shellName = getenv("SHELL");
#ifdef WIN32
if (shellName == NULL)
shellName = getenv("COMSPEC");
#endif
26 years ago
if (shellName == NULL)
shellName = DEFAULT_SHELL;
sys = pg_malloc(strlen(shellName) + 16);
#ifndef WIN32
22 years ago
sprintf(sys,
/* See EDITOR handling comment for an explaination */
"exec %s", shellName);
#else
sprintf(sys,
/* See EDITOR handling comment for an explaination */
"%s\"%s\"%s", SYSTEMQUOTE, shellName, SYSTEMQUOTE);
#endif
26 years ago
result = system(sys);
free(sys);
}
else
result = system(command);
26 years ago
if (result == 127 || result == -1)
{
psql_error("\\!: failed\n");
26 years ago
return false;
}
return true;
}