postgresql/src/bin/psql/command.c

1380 lines
27 KiB
C
Raw Normal View History

#include <config.h>
#include <c.h>
#include "command.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#ifndef WIN32
1999-11-05 00:14:30 +01:00
#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 */
1999-11-05 00:14:30 +01:00
static backslashResult exec_command(const char *cmd,
char *const * options,
const char *options_string,
PQExpBuffer query_buf,
PsqlSettings *pset);
static bool
1999-11-05 00:14:30 +01:00
do_edit(const char *filename_arg, PQExpBuffer query_buf);
static char *
1999-11-05 00:14:30 +01:00
unescape(const char *source, PsqlSettings *pset);
static bool
1999-11-05 00:14:30 +01:00
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(PsqlSettings *pset,
1999-11-05 00:14:30 +01:00
const char *line,
PQExpBuffer query_buf,
const char **end_of_cmd)
{
1999-11-05 00:14:30 +01:00
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. line[blank_loc] will now
1999-11-05 00:14:30 +01:00
* be the whitespace character or the \0 at the end
*
* Also look for a backslash, so stuff like \p\g works.
1999-11-05 00:14:30 +01:00
*/
blank_loc = strcspn(my_line, " \t\\");
1999-11-05 00:14:30 +01:00
if (my_line[blank_loc] == '\\')
{
continue_parse = &my_line[blank_loc];
my_line[blank_loc] = '\0';
}
1999-11-05 00:14:30 +01:00
/* do we have an option string? */
else if (my_line[blank_loc] != '\0')
{
options_string = &my_line[blank_loc + 1];
1999-11-05 00:14:30 +01:00
my_line[blank_loc] = '\0';
}
1999-11-05 00:14:30 +01:00
if (options_string)
{
char quote;
unsigned int pos;
options_string = &options_string[strspn(options_string, " \t")]; /* skip leading
* whitespace */
i = 0;
token = strtokx(options_string, " \t", "\"'`", '\\', &quote, &pos);
for (i = 0; token && i < 16; i++)
{
switch (quote)
{
case '"':
options[i] = unescape(token, pset);
break;
case '\'':
options[i] = xstrdup(token);
break;
case '`':
{
bool error = false;
FILE *fd = NULL;
char *file = unescape(token, pset);
PQExpBufferData output;
char buf[512];
size_t result;
fd = popen(file, "r");
if (!fd)
{
perror(file);
error = true;
}
if (!error)
{
initPQExpBuffer(&output);
do
{
result = fread(buf, 1, 512, fd);
if (ferror(fd))
{
perror(file);
error = true;
break;
}
appendBinaryPQExpBuffer(&output, buf, result);
} while (!feof(fd));
appendPQExpBufferChar(&output, '\0');
if (pclose(fd) == -1)
{
perror(file);
error = true;
}
}
if (!error)
{
if (output.data[strlen(output.data) - 1] == '\n')
output.data[strlen(output.data) - 1] = '\0';
}
free(file);
if (!error)
options[i] = output.data;
else
{
options[i] = xstrdup("");
termPQExpBuffer(&output);
}
break;
}
case 0:
default:
if (token[0] == '\\')
continue_parse = options_string + pos;
else if (token[0] == '$')
options[i] = xstrdup(interpolate_var(token + 1, pset));
else
options[i] = xstrdup(token);
break;
}
1999-11-05 00:14:30 +01:00
if (continue_parse)
break;
1999-11-05 00:14:30 +01:00
token = strtokx(NULL, " \t", "\"'`", '\\', &quote, &pos);
}
}
1999-11-05 00:14:30 +01:00
cmd = my_line;
status = exec_command(cmd, options, options_string, query_buf, pset);
1999-11-05 00:14:30 +01:00
if (status == CMD_UNKNOWN)
{
1999-11-05 00:14:30 +01:00
/*
* 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;
1999-11-05 00:14:30 +01:00
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);
}
1999-11-05 00:14:30 +01:00
if (status == CMD_UNKNOWN)
{
fprintf(stderr, "Unrecognized command: \\%s. Try \\? for help.\n", cmd);
status = CMD_ERROR;
}
1999-11-05 00:14:30 +01:00
if (continue_parse && *(continue_parse + 1) == '\\')
continue_parse += 2;
1999-11-05 00:14:30 +01:00
if (end_of_cmd)
{
if (continue_parse)
*end_of_cmd = line + (continue_parse - my_line);
else
*end_of_cmd = NULL;
}
1999-11-05 00:14:30 +01:00
/* clean up */
for (i = 0; i < 16 && options[i]; i++)
free(options[i]);
1999-11-05 00:14:30 +01:00
free(my_line);
1999-11-05 00:14:30 +01:00
return status;
}
static backslashResult
1999-11-05 00:14:30 +01:00
exec_command(const char *cmd,
char *const * options,
const char *options_string,
PQExpBuffer query_buf,
PsqlSettings *pset)
{
1999-11-05 00:14:30 +01:00
bool success = true; /* indicate here if the command ran ok or
* failed */
bool quiet = GetVariableBool(pset->vars, "quiet");
1999-11-05 00:14:30 +01:00
backslashResult status = CMD_SKIP_LINE;
1999-11-05 00:14:30 +01:00
/*
* \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);
}
1999-11-05 00:14:30 +01:00
/*
* \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);
}
}
1999-11-05 00:14:30 +01:00
/* \copy */
else if (strcmp(cmd, "copy") == 0)
success = do_copy(options_string, pset);
1999-11-05 00:14:30 +01:00
/* \copyright */
else if (strcmp(cmd, "copyright") == 0)
print_copyright();
1999-11-05 00:14:30 +01:00
/* \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;
}
}
1999-11-05 00:14:30 +01:00
/*
* \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;
1999-11-05 00:14:30 +01:00
/* \echo */
else if (strcmp(cmd, "echo") == 0)
{
int i;
1999-11-05 00:14:30 +01:00
for (i = 0; i < 16 && options[i]; i++)
fputs(options[i], stdout);
fputs("\n", stdout);
}
1999-11-05 00:14:30 +01:00
/*
* \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);
1999-11-05 00:14:30 +01:00
/* \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;
}
1999-11-05 00:14:30 +01:00
/* 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);
}
1999-11-05 00:14:30 +01:00
/* \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;
}
1999-11-05 00:14:30 +01:00
/* \o -- set query output */
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
success = setQFout(options[0], pset);
1999-11-05 00:14:30 +01:00
/* \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.");
}
1999-11-05 00:14:30 +01:00
/* \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);
}
1999-11-05 00:14:30 +01:00
/* \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);
}
1999-11-05 00:14:30 +01:00
/* reset(clear) the buffer */
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
{
resetPQExpBuffer(query_buf);
if (!quiet)
puts("Query buffer reset (cleared).");
}
1999-11-05 00:14:30 +01:00
/* \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];
1999-11-05 00:14:30 +01:00
success = saveHistory(fname);
1999-11-05 00:14:30 +01:00
if (success && !quiet && options[0])
printf("Wrote history to %s.\n", fname);
}
1999-11-05 00:14:30 +01:00
/* \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;
}
}
}
1999-11-05 00:14:30 +01:00
/* \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__
1999-11-05 00:14:30 +01:00
fd = popen(&options[0][1], "w");
#else
1999-11-05 00:14:30 +01:00
fd = popen(&options[0][1], "wb");
#endif
1999-11-05 00:14:30 +01:00
}
else
{
#ifndef __CYGWIN32__
1999-11-05 00:14:30 +01:00
fd = fopen(options[0], "w");
#else
1999-11-05 00:14:30 +01:00
fd = fopen(options[0], "wb");
#endif
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
if (!fd)
{
perror(options[0]);
success = false;
}
}
1999-11-05 00:14:30 +01:00
if (fd)
{
int result;
1999-11-05 00:14:30 +01:00
if (query_buf && query_buf->len > 0)
fprintf(fd, "%s\n", query_buf->data);
1999-11-05 00:14:30 +01:00
if (pipe)
result = pclose(fd);
else
result = fclose(fd);
1999-11-05 00:14:30 +01:00
if (result == EOF)
{
perror("close");
success = false;
}
}
}
1999-11-05 00:14:30 +01:00
/* \x -- toggle expanded table representation */
else if (strcmp(cmd, "x") == 0)
success = do_pset("expanded", NULL, &pset->popt, quiet);
1999-11-05 00:14:30 +01:00
/* list table rights (grant/revoke) */
else if (strcmp(cmd, "z") == 0)
success = permissionsList(options[0], pset);
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "!") == 0)
success = do_shell(options_string);
else if (strcmp(cmd, "?") == 0)
slashUsage(pset);
#ifdef NOT_USED
1999-11-05 00:14:30 +01:00
/*
* 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
1999-11-05 00:14:30 +01:00
else
status = CMD_UNKNOWN;
1999-11-05 00:14:30 +01:00
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 *
1999-11-05 00:14:30 +01:00
unescape(const char *source, PsqlSettings *pset)
{
1999-11-05 00:14:30 +01:00
unsigned char *p;
bool esc = false; /* Last character we saw was the escape
* character */
char *destination,
*tmp;
size_t length;
#ifdef USE_ASSERT_CHECKING
1999-11-05 00:14:30 +01:00
assert(source);
#endif
1999-11-05 00:14:30 +01:00
length = strlen(source) + 1;
1999-11-05 00:14:30 +01:00
tmp = destination = (char *) malloc(length);
if (!tmp)
{
perror("malloc");
exit(EXIT_FAILURE);
}
1999-11-05 00:14:30 +01:00
for (p = (char *) source; *p; p += PQmblen(p))
{
1999-11-05 00:14:30 +01:00
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++ = '$';
}
1999-11-05 00:14:30 +01:00
else
{
*tmp++ = *p;
esc = false;
}
}
1999-11-05 00:14:30 +01:00
*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
1999-11-05 00:14:30 +01:00
do_connect(const char *new_dbname, const char *new_user, PsqlSettings *pset)
{
1999-11-05 00:14:30 +01:00
PGconn *oldconn = pset->db;
const char *dbparam = NULL;
const char *userparam = NULL;
const char *pwparam = NULL;
1999-11-05 00:14:30 +01:00
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
1999-11-05 00:14:30 +01:00
/*
* 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
1999-11-05 00:14:30 +01:00
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;
}
}
1999-11-05 00:14:30 +01:00
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));
}
1999-11-05 00:14:30 +01:00
if (oldconn)
PQfinish(oldconn);
1999-11-05 00:14:30 +01:00
success = true;
}
1999-11-05 00:14:30 +01:00
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)
{
1999-11-05 00:14:30 +01:00
char *editorName;
char *sys;
int result;
#ifdef USE_ASSERT_CHECKING
1999-11-05 00:14:30 +01:00
assert(fname);
#else
1999-11-05 00:14:30 +01:00
if (!fname)
return false;
#endif
1999-11-05 00:14:30 +01:00
/* 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)
{
1999-11-05 00:14:30 +01:00
char fnametmp[64];
FILE *stream;
const char *fname;
bool error = false;
#ifndef WIN32
1999-11-05 00:14:30 +01:00
struct stat before,
after;
#endif
#ifdef USE_ASSERT_CHECKING
1999-11-05 00:14:30 +01:00
assert(query_buf);
#else
1999-11-05 00:14:30 +01:00
if (!query_buf)
return false;
#endif
1999-11-05 00:14:30 +01:00
if (filename_arg)
fname = filename_arg;
1999-11-05 00:14:30 +01:00
else
{
/* make a temp file to edit */
#ifndef WIN32
1999-11-05 00:14:30 +01:00
mode_t oldumask;
1999-11-05 00:14:30 +01:00
sprintf(fnametmp, "/tmp/psql.edit.%ld.%ld", (long) geteuid(), (long) getpid());
#else
1999-11-05 00:14:30 +01:00
GetTempFileName(".", "psql", 0, fnametmp);
#endif
1999-11-05 00:14:30 +01:00
fname = (const char *) fnametmp;
#ifndef WIN32
1999-11-05 00:14:30 +01:00
oldumask = umask(0177);
#endif
1999-11-05 00:14:30 +01:00
stream = fopen(fname, "w");
#ifndef WIN32
1999-11-05 00:14:30 +01:00
umask(oldumask);
#endif
1999-11-05 00:14:30 +01:00
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);
}
}
1999-11-05 00:14:30 +01:00
#ifndef WIN32
if (!error && stat(fname, &before) != 0)
{
perror(fname);
error = true;
}
#endif
1999-11-05 00:14:30 +01:00
/* call editor */
if (!error)
error = !editFile(fname);
#ifndef WIN32
1999-11-05 00:14:30 +01:00
if (!error && stat(fname, &after) != 0)
{
perror(fname);
error = true;
}
1999-11-05 00:14:30 +01:00
if (!error && before.st_mtime != after.st_mtime)
{
#else
1999-11-05 00:14:30 +01:00
if (!error)
{
#endif
1999-11-05 00:14:30 +01:00
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);
}
1999-11-05 00:14:30 +01:00
/* remove temp file */
if (!filename_arg)
remove(fname);
}
1999-11-05 00:14:30 +01:00
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)
{
1999-11-05 00:14:30 +01:00
FILE *fd;
int result;
1999-11-05 00:14:30 +01:00
if (!filename)
return false;
#ifdef __CYGWIN32__
1999-11-05 00:14:30 +01:00
fd = fopen(filename, "rb");
#else
1999-11-05 00:14:30 +01:00
fd = fopen(filename, "r");
#endif
1999-11-05 00:14:30 +01:00
if (!fd)
{
perror(filename);
return false;
}
1999-11-05 00:14:30 +01:00
result = MainLoop(pset, fd);
fclose(fd);
return (result == EXIT_SUCCESS);
}
/*
* do_pset
*
*/
static const char *
_align2string(enum printFormat in)
{
1999-11-05 00:14:30 +01:00
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
1999-11-05 00:14:30 +01:00
do_pset(const char *param, const char *value, printQueryOpt * popt, bool quiet)
{
1999-11-05 00:14:30 +01:00
size_t vallen = 0;
#ifdef USE_ASSERT_CHECKING
1999-11-05 00:14:30 +01:00
assert(param);
#else
1999-11-05 00:14:30 +01:00
if (!param)
return false;
#endif
1999-11-05 00:14:30 +01:00
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));
}
1999-11-05 00:14:30 +01:00
/* set border style/width */
else if (strcmp(param, "border") == 0)
{
if (value)
popt->topt.border = atoi(value);
1999-11-05 00:14:30 +01:00
if (!quiet)
printf("Border style is %d.\n", popt->topt.border);
}
1999-11-05 00:14:30 +01:00
/* 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");
}
1999-11-05 00:14:30 +01:00
/* 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 : "");
}
1999-11-05 00:14:30 +01:00
/* 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);
}
1999-11-05 00:14:30 +01:00
/* 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");
}
}
1999-11-05 00:14:30 +01:00
/* 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");
}
}
1999-11-05 00:14:30 +01:00
/* 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.");
}
}
1999-11-05 00:14:30 +01:00
else
{
fprintf(stderr, "Unknown option: %s\n", param);
return false;
}
return true;
}
#define DEFAULT_SHELL "/bin/sh"
static bool
do_shell(const char *command)
{
1999-11-05 00:14:30 +01:00
int result;
1999-11-05 00:14:30 +01:00
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);
1999-11-05 00:14:30 +01:00
if (result == 127 || result == -1)
{
perror("system");
return false;
}
return true;
}