1229 lines
27 KiB
C
1229 lines
27 KiB
C
|
#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", "\"'`", '\\', "e, &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", "\"'`", '\\', "e, &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;
|
||
|
}
|