/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright 2000 by PostgreSQL Global Development Group
 *
 * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.58 2001/09/10 14:51:33 momjian Exp $
 */
#include "postgres_fe.h"
#include "command.h"

#include <errno.h>
#include <assert.h>
#include <ctype.h>
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifndef WIN32
#include <sys/types.h>			/* for umask() */
#include <sys/stat.h>			/* for stat() */
#include <fcntl.h>				/* open() flags */
#include <unistd.h>				/* for geteuid(), getpid(), stat() */
#else
#include <win32.h>
#include <io.h>
#include <fcntl.h>
#endif

#include "libpq-fe.h"
#include "pqexpbuffer.h"

#include "common.h"
#include "copy.h"
#include "describe.h"
#include "help.h"
#include "input.h"
#include "large_obj.h"
#include "mainloop.h"
#include "print.h"
#include "settings.h"
#include "variables.h"

#ifdef MULTIBYTE
#include "mb/pg_wchar.h"
#else
/* Grand unified hard-coded badness */
#define pg_encoding_to_char(x) "SQL_ASCII"
#endif


/* functions for use in this file */

static backslashResult exec_command(const char *cmd,
			 const char *options_string,
			 const char **continue_parse,
			 PQExpBuffer query_buf);

enum option_type
{
	OT_NORMAL, OT_SQLID, OT_FILEPIPE
};
static char *scan_option(char **string, enum option_type type, char *quote);
static char *unescape(const unsigned char *source, size_t len);

static bool do_edit(const char *filename_arg, PQExpBuffer query_buf);
static bool do_connect(const char *new_dbname, const char *new_user);
static bool do_shell(const char *command);



/*----------
 * HandleSlashCmds:
 *
 * Handles all the different commands that start with '\',
 * ordinarily called by MainLoop().
 *
 * 'line' is the current input line, which should not start with a '\'
 * but with the actual command name
 * (that is taken care of by MainLoop)
 *
 * 'query_buf' contains the query-so-far, which may be modified by
 * execution of the backslash command (for example, \r clears it)
 * query_buf can be NULL if there is no query so far.
 *
 * Returns a status code indicating what action is desired, see command.h.
 *----------
 */

backslashResult
HandleSlashCmds(const char *line,
				PQExpBuffer query_buf,
				const char **end_of_cmd)
{
	backslashResult status = CMD_SKIP_LINE;
	char	   *my_line;
	char	   *options_string = NULL;
	size_t		blank_loc;
	const char *continue_parse = NULL;	/* tell the mainloop where the
										 * backslash command ended */

#ifdef USE_ASSERT_CHECKING
	assert(line);
#endif

	my_line = xstrdup(line);

	/*
	 * Find the first whitespace. line[blank_loc] will now be the
	 * whitespace character or the \0 at the end
	 *
	 * Also look for a backslash, so stuff like \p\g works.
	 */
	blank_loc = strcspn(my_line, " \t\n\r\\");

	if (my_line[blank_loc] == '\\')
	{
		continue_parse = &my_line[blank_loc];
		my_line[blank_loc] = '\0';
		/* If it's a double backslash, we skip it. */
		if (my_line[blank_loc + 1] == '\\')
			continue_parse += 2;
	}
	/* do we have an option string? */
	else if (my_line[blank_loc] != '\0')
	{
		options_string = &my_line[blank_loc + 1];
		my_line[blank_loc] = '\0';
	}

	status = exec_command(my_line, options_string, &continue_parse, query_buf);

	if (status == CMD_UNKNOWN)
	{

		/*
		 * If the command was not recognized, try to parse it as a one-letter
		 * command with immediately following argument (a still-supported,
		 * but no longer encouraged, syntax).
		 */
		char		new_cmd[2];

		new_cmd[0] = my_line[0];
		new_cmd[1] = '\0';

		/* use line for options, because my_line was clobbered above */
		status = exec_command(new_cmd, line + 1, &continue_parse, query_buf);

		/* continue_parse must be relative to my_line for calculation below */
		continue_parse += my_line - line;

#if 0							/* turned out to be too annoying */
		if (status != CMD_UNKNOWN && isalpha((unsigned char) new_cmd[0]))
			psql_error("Warning: This syntax is deprecated.\n");
#endif
	}

	if (status == CMD_UNKNOWN)
	{
		if (pset.cur_cmd_interactive)
			fprintf(stderr, gettext("Invalid command \\%s. Try \\? for help.\n"), my_line);
		else
			psql_error("invalid command \\%s\n", my_line);
		status = CMD_ERROR;
	}

	if (continue_parse && *continue_parse && *(continue_parse + 1) == '\\')
		continue_parse += 2;

	if (end_of_cmd)
	{
		if (continue_parse)
			*end_of_cmd = line + (continue_parse - my_line);
		else
			*end_of_cmd = line + strlen(line);
	}

	free(my_line);

	return status;
}



static backslashResult
exec_command(const char *cmd,
			 const char *options_string,
			 const char **continue_parse,
			 PQExpBuffer query_buf)
{
	bool		success = true; /* indicate here if the command ran ok or
								 * failed */
	bool		quiet = QUIET();
	backslashResult status = CMD_SKIP_LINE;
	char	   *string,
			   *string_cpy,
			   *val;

	/*
	 * The 'string' variable will be overwritten to point to the next
	 * token, hence we need an extra pointer so we can free this at the
	 * end.
	 */
	if (options_string)
		string = string_cpy = xstrdup(options_string);
	else
		string = string_cpy = NULL;

	/*
	 * \a -- toggle field alignment This makes little sense but we keep it
	 * around.
	 */
	if (strcmp(cmd, "a") == 0)
	{
		if (pset.popt.topt.format != PRINT_ALIGNED)
			success = do_pset("format", "aligned", &pset.popt, quiet);
		else
			success = do_pset("format", "unaligned", &pset.popt, quiet);
	}

	/* \C -- override table title (formerly change HTML caption) */
	else if (strcmp(cmd, "C") == 0)
	{
		char	   *opt = scan_option(&string, OT_NORMAL, NULL);

		success = do_pset("title", opt, &pset.popt, quiet);
		free(opt);
	}

	/*----------
	 * \c or \connect -- connect to new database or as different user
	 *
	 * \c foo bar  connect to db "foo" as user "bar"
	 * \c foo [-]  connect to db "foo" as current user
	 * \c - bar    connect to current db as user "bar"
	 * \c		   connect to default db as default user
	 *----------
	 */
	else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
	{
		char	   *opt1,
				   *opt2;
		char		opt1q,
					opt2q;

		opt1 = scan_option(&string, OT_SQLID, &opt1q);
		opt2 = scan_option(&string, OT_SQLID, &opt2q);

		if (opt2)
			/* gave username */
			success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || strcmp(opt1, "") == 0) ? "" : opt1,
								 !opt2q && (strcmp(opt2, "-") == 0 || strcmp(opt2, "") == 0) ? "" : opt2);
		else if (opt1)
			/* gave database name */
			success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || strcmp(opt1, "") == 0) ? "" : opt1, "");
		else
			/* connect to default db as default user */
			success = do_connect(NULL, NULL);

		free(opt1);
		free(opt2);
	}

	/* \cd */
	else if (strcmp(cmd, "cd") == 0)
	{
		char   *opt = scan_option(&string, OT_NORMAL, NULL);
		char   *dir;

		if (opt)
			dir = opt;
		else
		{
#ifndef WIN32
			struct passwd *pw;

			pw = getpwuid(geteuid());
			if (!pw)
			{
				psql_error("could not get home directory: %s\n", strerror(errno));
				exit(EXIT_FAILURE);
			}
			dir = pw->pw_dir;
#else /* WIN32 */
			/* On Windows, 'cd' without arguments prints the current
               directory, so if someone wants to code this here
               instead... */
			dir = "/";
#endif /* WIN32 */
		}

		if (chdir(dir) == -1)
		{
			psql_error("\\%s: could not change directory to '%s': %s\n",
					   cmd, dir, strerror(errno));
			success = false;
		}

		if (opt)
			free(opt);
	}

	/* \copy */
	else if (strcasecmp(cmd, "copy") == 0)
	{
		success = do_copy(options_string);
		if (options_string)
			string += strlen(string);
	}

	/* \copyright */
	else if (strcmp(cmd, "copyright") == 0)
		print_copyright();

	/* \d* commands */
	else if (cmd[0] == 'd')
	{
		char	   *name;
		bool		show_verbose;

		name = scan_option(&string, OT_SQLID, NULL);
		show_verbose = strchr(cmd, '+') ? true : false;

		switch (cmd[1])
		{
			case '\0':
			case '+':
				if (name)
					success = describeTableDetails(name, show_verbose);
				else
					/* standard listing of interesting things */
					success = listTables("tvs", NULL, show_verbose);
				break;
			case 'a':
				success = describeAggregates(name);
				break;
			case 'd':
				success = objectDescription(name);
				break;
			case 'f':
				success = describeFunctions(name, show_verbose);
				break;
			case 'l':
				success = do_lo_list();
				break;
			case 'o':
				success = describeOperators(name);
				break;
			case 'p':
				success = permissionsList(name);
				break;
			case 'T':
				success = describeTypes(name, show_verbose);
				break;
			case 't':
			case 'v':
			case 'i':
			case 's':
			case 'S':
				success = listTables(&cmd[1], name, show_verbose);
				break;
			case 'u':
				success = describeUsers(name);
  				break;
			default:
				status = CMD_UNKNOWN;
		}
		free(name);
	}


	/*
	 * \e or \edit -- edit the current query buffer (or a file and make it
	 * the query buffer
	 */
	else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
	{
		char	   *fname;

		if (!query_buf)
		{
			psql_error("no query buffer\n");
			status = CMD_ERROR;
		}
		else
		{
			fname = scan_option(&string, OT_NORMAL, NULL);
			status = do_edit(fname, query_buf) ? CMD_NEWEDIT : CMD_ERROR;
			free(fname);
		}
	}

	/* \echo and \qecho */
	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
	{
		char	   *value;
		char		quoted;
		bool		no_newline = false;
		bool		first = true;
		FILE	   *fout;

		if (strcmp(cmd, "qecho") == 0)
			fout = pset.queryFout;
		else
			fout = stdout;

		while ((value = scan_option(&string, OT_NORMAL, &quoted)))
		{
			if (!quoted && strcmp(value, "-n") == 0)
				no_newline = true;
			else
			{
				if (first)
					first = false;
				else
					fputc(' ', fout);
				fputs(value, fout);
			}
			free(value);
		}
		if (!no_newline)
			fputs("\n", fout);
	}

	/* \encoding -- set/show client side encoding */
	else if (strcmp(cmd, "encoding") == 0)
	{
		char	   *encoding = scan_option(&string, OT_NORMAL, NULL);

		if (!encoding)
			/* show encoding */
			puts(pg_encoding_to_char(pset.encoding));
		else
		{
#ifdef MULTIBYTE
			/* set encoding */
			if (PQsetClientEncoding(pset.db, encoding) == -1)
				psql_error("%s: invalid encoding name\n", encoding);

			else
			{
				/* save encoding info into psql internal data */
				pset.encoding = PQclientEncoding(pset.db);
				SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
			}
#else
			psql_error("\\%s: multibyte support is not enabled\n", cmd);
#endif
			free(encoding);
		}
	}

	/* \f -- change field separator */
	else if (strcmp(cmd, "f") == 0)
	{
		char	   *fname = scan_option(&string, OT_NORMAL, NULL);

		success = do_pset("fieldsep", fname, &pset.popt, quiet);
		free(fname);
	}

	/* \g means send query */
	else if (strcmp(cmd, "g") == 0)
	{
		char	   *fname = scan_option(&string, OT_FILEPIPE, NULL);

		if (!fname)
			pset.gfname = NULL;
		else
			pset.gfname = xstrdup(fname);
		free(fname);
		status = CMD_SEND;
	}

	/* help */
	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
	{
		helpSQL(options_string ? &options_string[strspn(options_string, " \t\n\r")] : NULL);
		/* set pointer to end of line */
		if (string)
			string += strlen(string);
	}

	/* HTML mode */
	else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
	{
		if (pset.popt.topt.format != PRINT_HTML)
			success = do_pset("format", "html", &pset.popt, quiet);
		else
			success = do_pset("format", "aligned", &pset.popt, quiet);
	}


	/* \i is include file */
	else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
	{
		char	   *fname = scan_option(&string, OT_NORMAL, NULL);

		if (!fname)
		{
			psql_error("\\%s: missing required argument\n", cmd);
			success = false;
		}
		else
		{
			success = (process_file(fname) == EXIT_SUCCESS);
			free(fname);
		}
	}

	/* \l is list databases */
	else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0)
		success = listAllDbs(false);
	else if (strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
		success = listAllDbs(true);

	/*
	 * large object things
	 */
	else if (strncmp(cmd, "lo_", 3) == 0)
	{
		char	   *opt1,
				   *opt2;

		opt1 = scan_option(&string, OT_NORMAL, NULL);
		opt2 = scan_option(&string, OT_NORMAL, NULL);

		if (strcmp(cmd + 3, "export") == 0)
		{
			if (!opt2)
			{
				psql_error("\\%s: missing required argument\n", cmd);
				success = false;
			}
			else
				success = do_lo_export(opt1, opt2);
		}

		else if (strcmp(cmd + 3, "import") == 0)
		{
			if (!opt1)
			{
				psql_error("\\%s: missing required argument\n", cmd);
				success = false;
			}
			else
				success = do_lo_import(opt1, opt2);
		}

		else if (strcmp(cmd + 3, "list") == 0)
			success = do_lo_list();

		else if (strcmp(cmd + 3, "unlink") == 0)
		{
			if (!opt1)
			{
				psql_error("\\%s: missing required argument\n", cmd);
				success = false;
			}
			else
				success = do_lo_unlink(opt1);
		}

		else
			status = CMD_UNKNOWN;

		free(opt1);
		free(opt2);
	}


	/* \o -- set query output */
	else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
	{
		char	   *fname = scan_option(&string, OT_FILEPIPE, NULL);

		success = setQFout(fname);
		free(fname);
	}

	/* \p prints the current query buffer */
	else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
	{
		if (query_buf && query_buf->len > 0)
			puts(query_buf->data);
		else if (!quiet)
			puts(gettext("Query buffer is empty."));
		fflush(stdout);
	}

	/* \pset -- set printing parameters */
	else if (strcmp(cmd, "pset") == 0)
	{
		char	   *opt0 = scan_option(&string, OT_NORMAL, NULL);
		char	   *opt1 = scan_option(&string, OT_NORMAL, NULL);

		if (!opt0)
		{
			psql_error("\\%s: missing required argument\n", cmd);
			success = false;
		}
		else
			success = do_pset(opt0, opt1, &pset.popt, quiet);

		free(opt0);
		free(opt1);
	}

	/* \q or \quit */
	else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
		status = CMD_TERMINATE;

	/* reset(clear) the buffer */
	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
	{
		resetPQExpBuffer(query_buf);
		if (!quiet)
			puts(gettext("Query buffer reset (cleared)."));
	}

	/* \s save history in a file or show it on the screen */
	else if (strcmp(cmd, "s") == 0)
	{
		char	   *fname = scan_option(&string, OT_NORMAL, NULL);

		success = saveHistory(fname ? fname : "/dev/tty");

		if (success && !quiet && fname)
			printf(gettext("Wrote history to %s.\n"), fname);
		free(fname);
	}

	/* \set -- generalized set variable/option command */
	else if (strcmp(cmd, "set") == 0)
	{
		char	   *opt0 = scan_option(&string, OT_NORMAL, NULL);

		if (!opt0)
		{
			/* list all variables */

			/*
			 * XXX This is in utter violation of the GetVariable
			 * abstraction, but I have not bothered to do it better.
			 */
			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
		{

			/*
			 * Set variable to the concatenation of the arguments.
			 */
			char	   *newval = NULL;
			char	   *opt;

			opt = scan_option(&string, OT_NORMAL, NULL);
			newval = xstrdup(opt ? opt : "");
			free(opt);

			while ((opt = scan_option(&string, OT_NORMAL, NULL)))
			{
				newval = realloc(newval, strlen(newval) + strlen(opt) + 1);
				if (!newval)
				{
					psql_error("out of memory\n");
					exit(EXIT_FAILURE);
				}
				strcat(newval, opt);
				free(opt);
			}

			if (!SetVariable(pset.vars, opt0, newval))
			{
				psql_error("\\%s: error\n", cmd);
				success = false;
			}
			free(newval);
		}
		free(opt0);
	}

	/* \t -- turn off headers and row count */
	else if (strcmp(cmd, "t") == 0)
		success = do_pset("tuples_only", NULL, &pset.popt, quiet);


	/* \T -- define html <table ...> attributes */
	else if (strcmp(cmd, "T") == 0)
	{
		char	   *value = scan_option(&string, OT_NORMAL, NULL);

		success = do_pset("tableattr", value, &pset.popt, quiet);
		free(value);
	}

	/* \unset */
	else if (strcmp(cmd, "unset") == 0)
	{
		char	   *opt = scan_option(&string, OT_NORMAL, NULL);

		if (!opt)
		{
			psql_error("\\%s: missing required argument\n", cmd);
			success = false;
		}
		if (!SetVariable(pset.vars, opt, NULL))
		{
			psql_error("\\%s: error\n", cmd);
			success = false;
		}
		free(opt);
	}

	/* \w -- write query buffer to file */
	else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
	{
		FILE	   *fd = NULL;
		bool		is_pipe = false;
		char	   *fname = NULL;

		if (!query_buf)
		{
			psql_error("no query buffer\n");
			status = CMD_ERROR;
		}
		else
		{
			fname = scan_option(&string, OT_FILEPIPE, NULL);

			if (!fname)
			{
				psql_error("\\%s: missing required argument\n", cmd);
				success = false;
			}
			else
			{
				if (fname[0] == '|')
				{
					is_pipe = true;
					fd = popen(&fname[1], "w");
				}
				else
					fd = fopen(fname, "w");

				if (!fd)
				{
					psql_error("%s: %s\n", fname, strerror(errno));
					success = false;
				}
			}
		}

		if (fd)
		{
			int			result;

			if (query_buf && query_buf->len > 0)
				fprintf(fd, "%s\n", query_buf->data);

			if (is_pipe)
				result = pclose(fd);
			else
				result = fclose(fd);

			if (result == EOF)
			{
				psql_error("%s: %s\n", fname, strerror(errno));
				success = false;
			}
		}

		free(fname);
	}

	/* \x -- toggle expanded table representation */
	else if (strcmp(cmd, "x") == 0)
		success = do_pset("expanded", NULL, &pset.popt, quiet);


	/* \z -- list table rights (grant/revoke) */
	else if (strcmp(cmd, "z") == 0)
	{
		char	   *opt = scan_option(&string, OT_SQLID, NULL);

		success = permissionsList(opt);
		free(opt);
	}

	/* \! -- shell escape */
	else if (strcmp(cmd, "!") == 0)
	{
		success = do_shell(options_string);
		/* wind pointer to end of line */
		if (string)
			string += strlen(string);
	}

	/* \? -- slash command help */
	else if (strcmp(cmd, "?") == 0)
		slashUsage();

#if 0

	/*
	 * These commands don't do anything. I just use them to test the
	 * parser.
	 */
	else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0)
	{
		int			i = 0;
		char	   *value;

		fprintf(stderr, "+ optstr = |%s|\n", options_string);
		while ((value = scan_option(&string, OT_NORMAL, NULL)))
		{
			fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value);
			free(value);
		}
	}
#endif

	else
		status = CMD_UNKNOWN;

	if (!success)
		status = CMD_ERROR;

	/* eat the rest of the options string */
	while ((val = scan_option(&string, OT_NORMAL, NULL)))
	{
		if (status != CMD_UNKNOWN)
			psql_error("\\%s: extra argument '%s' ignored\n", cmd, val);
	}

	if (options_string && continue_parse)
		*continue_parse = options_string + (string - string_cpy);
	free(string_cpy);

	return status;
}



/*
 * scan_option()
 */
static char *
scan_option(char **string, enum option_type type, char *quote)
{
	unsigned int pos = 0;
	char	   *options_string;
	char	   *return_val;

	if (quote)
		*quote = 0;

	if (!string || !(*string))
		return NULL;

	options_string = *string;
	/* skip leading whitespace */
	pos += strspn(options_string + pos, " \t\n\r");

	switch (options_string[pos])
	{

			/*
			 * Double quoted string
			 */
		case '"':
			{
				unsigned int jj;
				unsigned short int bslash_count = 0;

				/* scan for end of quote */
				for (jj = pos + 1; options_string[jj]; jj += PQmblen(&options_string[jj], pset.encoding))
				{
					if (options_string[jj] == '"' && bslash_count % 2 == 0)
						break;

					if (options_string[jj] == '\\')
						bslash_count++;
					else
						bslash_count = 0;
				}

				if (options_string[jj] == 0)
				{
					psql_error("parse error at the end of line\n");
					*string = &options_string[jj];
					return NULL;
				}

				return_val = malloc(jj - pos + 2);
				if (!return_val)
				{
					psql_error("out of memory\n");
					exit(EXIT_FAILURE);
				}

				/*
				 * If this is expected to be an SQL identifier like option
				 * then we strip out the double quotes
				 */
				if (type == OT_SQLID)
				{
					unsigned int k,
								cc;

					bslash_count = 0;
					cc = 0;
					for (k = pos + 1; options_string[k]; k += PQmblen(&options_string[k], pset.encoding))
					{
						if (options_string[k] == '"' && bslash_count % 2 == 0)
							break;

						if (options_string[jj] == '\\')
							bslash_count++;
						else
							bslash_count = 0;

						return_val[cc++] = options_string[k];
					}
					return_val[cc] = '\0';
				}

				else
				{
					strncpy(return_val, &options_string[pos], jj - pos + 1);
					return_val[jj - pos + 1] = '\0';
				}

				*string = options_string + jj + 1;
				if (quote)
					*quote = '"';

				return return_val;
			}

			/*
			 * A single quote has a psql internal meaning, such as for
			 * delimiting file names, and it also allows for such escape
			 * sequences as \t.
			 */
		case '\'':
			{
				unsigned int jj;
				unsigned short int bslash_count = 0;

				for (jj = pos + 1; options_string[jj]; jj += PQmblen(&options_string[jj], pset.encoding))
				{
					if (options_string[jj] == '\'' && bslash_count % 2 == 0)
						break;

					if (options_string[jj] == '\\')
						bslash_count++;
					else
						bslash_count = 0;
				}

				if (options_string[jj] == 0)
				{
					psql_error("parse error at the end of line\n");
					*string = &options_string[jj];
					return NULL;
				}

				return_val = unescape(&options_string[pos + 1], jj - pos - 1);
				*string = &options_string[jj + 1];
				if (quote)
					*quote = '\'';
				return return_val;
			}

			/*
			 * Backticks are for command substitution, like in shells
			 */
		case '`':
			{
				bool		error = false;
				FILE	   *fd = NULL;
				char	   *file;
				PQExpBufferData output;
				char		buf[512];
				size_t		result,
							len;

				len = strcspn(options_string + pos + 1, "`");
				if (options_string[pos + 1 + len] == 0)
				{
					psql_error("parse error at the end of line\n");
					*string = &options_string[pos + 1 + len];
					return NULL;
				}

				options_string[pos + 1 + len] = '\0';
				file = options_string + pos + 1;

				fd = popen(file, "r");
				if (!fd)
				{
					psql_error("%s: %s\n", file, strerror(errno));
					error = true;
				}

				if (!error)
				{
					initPQExpBuffer(&output);

					do
					{
						result = fread(buf, 1, 512, fd);
						if (ferror(fd))
						{
							psql_error("%s: %s\n", file, strerror(errno));
							error = true;
							break;
						}
						appendBinaryPQExpBuffer(&output, buf, result);
					} while (!feof(fd));
					appendPQExpBufferChar(&output, '\0');

					if (pclose(fd) == -1)
					{
						psql_error("%s: %s\n", file, strerror(errno));
						error = true;
					}
				}

				if (!error)
				{
					if (output.data[strlen(output.data) - 1] == '\n')
						output.data[strlen(output.data) - 1] = '\0';
				}

				if (!error)
					return_val = output.data;
				else
				{
					return_val = xstrdup("");
					termPQExpBuffer(&output);
				}
				options_string[pos + 1 + len] = '`';
				*string = options_string + pos + len + 2;
				if (quote)
					*quote = '`';
				return return_val;
			}

			/*
			 * end of line
			 */
		case 0:
			*string = &options_string[pos];
			return NULL;

			/*
			 * Variable substitution
			 */
		case ':':
			{
				size_t		token_end;
				const char *value;
				char		save_char;

				token_end = strcspn(&options_string[pos + 1], " \t\n\r");
				save_char = options_string[pos + token_end + 1];
				options_string[pos + token_end + 1] = '\0';
				value = GetVariable(pset.vars, options_string + pos + 1);
				if (!value)
					value = "";
				return_val = xstrdup(value);
				options_string[pos + token_end + 1] = save_char;
				*string = &options_string[pos + token_end + 1];
				return return_val;
			}

			/*
			 * Next command
			 */
		case '\\':
			*string = options_string + pos;
			return NULL;
			break;

			/*
			 * | could be the beginning of a pipe if so, take rest of line
			 * as command
			 */
		case '|':
			if (type == OT_FILEPIPE)
			{
				*string += strlen(options_string + pos);
				return xstrdup(options_string + pos);
				break;
			}
			/* fallthrough for other option types */

			/*
			 * A normal word
			 */
		default:
			{
				size_t		token_end;
				char	   *cp;

				token_end = strcspn(&options_string[pos], " \t\n\r");
				return_val = malloc(token_end + 1);
				if (!return_val)
				{
					psql_error("out of memory\n");
					exit(EXIT_FAILURE);
				}
				strncpy(return_val, &options_string[pos], token_end);
				return_val[token_end] = 0;

				if (type == OT_SQLID)
					for (cp = return_val; *cp; cp += PQmblen(cp, pset.encoding))
						if (isupper((unsigned char) *cp))
							*cp = tolower((unsigned char) *cp);

				*string = &options_string[pos + token_end];
				return return_val;
			}

	}
}



/*
 * unescape
 *
 * Replaces \n, \t, and the like.
 *
 * The return value is malloc()'ed.
 */
static char *
unescape(const unsigned char *source, size_t len)
{
	const unsigned char *p;
	bool		esc = false;	/* Last character we saw was the escape
								 * character */
	char	   *destination,
			   *tmp;
	size_t		length;

#ifdef USE_ASSERT_CHECKING
	assert(source);
#endif

	length = Min(len, strlen(source)) + 1;

	tmp = destination = malloc(length);
	if (!tmp)
	{
		psql_error("out of memory\n");
		exit(EXIT_FAILURE);
	}

	for (p = source; p - source < len && *p; p += PQmblen(p, pset.encoding))
	{
		if (esc)
		{
			char		c;

			switch (*p)
			{
				case 'n':
					c = '\n';
					break;
				case 't':
					c = '\t';
					break;
				case 'b':
					c = '\b';
					break;
				case 'r':
					c = '\r';
					break;
				case 'f':
					c = '\f';
					break;
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					{
						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
		{
			int			i;
			const unsigned char *mp = p;

			for (i = 0; i < PQmblen(p, pset.encoding); i++)
				*tmp++ = *mp++;
			esc = false;
		}
	}

	*tmp = '\0';
	return destination;
}



/* do_connect
 * -- handler for \connect
 *
 * Connects to a database (new_dbname) as a certain user (new_user).
 * The new user can be NULL. A db name of "-" is the same as the old one.
 * (That is, the one currently in pset. But pset.db can also be NULL. A NULL
 * dbname is handled by libpq.)
 * Returns true if all ok, false if the new connection couldn't be established.
 * The old connection will be kept if the session is interactive.
 */
static bool
do_connect(const char *new_dbname, const char *new_user)
{
	PGconn	   *oldconn = pset.db;
	const char *dbparam = NULL;
	const char *userparam = NULL;
	const char *pwparam = NULL;
	char	   *prompted_password = NULL;
	bool		need_pass;
	bool		success = false;

	/* Delete variables (in case we fail before setting them anew) */
	SetVariable(pset.vars, "DBNAME", NULL);
	SetVariable(pset.vars, "USER", NULL);
	SetVariable(pset.vars, "HOST", NULL);
	SetVariable(pset.vars, "PORT", NULL);
	SetVariable(pset.vars, "ENCODING", NULL);

	/* If dbname is "" then use old name, else new one (even if NULL) */
	if (oldconn && new_dbname && PQdb(oldconn) && strcmp(new_dbname, "") == 0)
		dbparam = PQdb(oldconn);
	else
		dbparam = new_dbname;

	/* If user is "" then use the old one */
	if (new_user && PQuser(oldconn) && strcmp(new_user, "") == 0)
		userparam = PQuser(oldconn);
	else
		userparam = new_user;

	/* need to prompt for password? */
	if (pset.getPassword)
		pwparam = prompted_password = simple_prompt("Password: ", 100, false);	/* need to save for
																				 * free() */

	/*
	 * Use old password if no new one given (if you didn't have an old
	 * one, fine)
	 */
	if (!pwparam && oldconn)
		pwparam = PQpass(oldconn);

	do
	{
		need_pass = false;
		pset.db = PQsetdbLogin(PQhost(oldconn), PQport(oldconn),
							   NULL, NULL, dbparam, userparam, pwparam);

		if (PQstatus(pset.db) == CONNECTION_BAD &&
			strcmp(PQerrorMessage(pset.db), "fe_sendauth: no password supplied\n") == 0 &&
			!feof(stdin))
		{
			PQfinish(pset.db);
			need_pass = true;
			free(prompted_password);
			prompted_password = NULL;
			pwparam = prompted_password = simple_prompt("Password: ", 100, false);
		}
	} while (need_pass);

	free(prompted_password);

	/*
	 * If connection failed, try at least keep the old one. That's
	 * probably more convenient than just kicking you out of the program.
	 */
	if (!pset.db || PQstatus(pset.db) == CONNECTION_BAD)
	{
		if (pset.cur_cmd_interactive)
		{
			psql_error("%s", PQerrorMessage(pset.db));
			PQfinish(pset.db);
			if (oldconn)
			{
				fputs(gettext("Previous connection kept\n"), stderr);
				pset.db = oldconn;
			}
			else
				pset.db = NULL;
		}
		else
		{

			/*
			 * we don't want unpredictable things to happen in scripting
			 * mode
			 */
			psql_error("\\connect: %s", PQerrorMessage(pset.db));
			PQfinish(pset.db);
			if (oldconn)
				PQfinish(oldconn);
			pset.db = NULL;
		}
	}
	else
	{
		if (!QUIET())
		{
			if (userparam != new_user)	/* no new user */
				printf(gettext("You are now connected to database %s.\n"), dbparam);
			else if (dbparam != new_dbname)		/* no new db */
				printf(gettext("You are now connected as new user %s.\n"), new_user);
			else
/* both new */
				printf(gettext("You are now connected to database %s as user %s.\n"),
					   PQdb(pset.db), PQuser(pset.db));
		}

		if (oldconn)
			PQfinish(oldconn);

		success = true;
	}

	PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
	pset.encoding = PQclientEncoding(pset.db);

	/* Update variables */
	SetVariable(pset.vars, "DBNAME", PQdb(pset.db));
	SetVariable(pset.vars, "USER", PQuser(pset.db));
	SetVariable(pset.vars, "HOST", PQhost(pset.db));
	SetVariable(pset.vars, "PORT", PQport(pset.db));
	SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));

	pset.issuper = test_superuser(PQuser(pset.db));

	return success;
}



/*
 * Test if the given user is a database superuser.
 * (Is used to set up the prompt right.)
 */
bool
test_superuser(const char *username)
{
	PGresult   *res;
	char		buf[64 + NAMEDATALEN];
	bool		answer;

	if (!username)
		return false;

	sprintf(buf, "SELECT usesuper FROM pg_user WHERE usename = '%.*s'", NAMEDATALEN, username);
	res = PSQLexec(buf);

	answer =
		(PQntuples(res) > 0 && PQnfields(res) > 0
		 && !PQgetisnull(res, 0, 0)
		 && PQgetvalue(res, 0, 0)
		 && strcmp(PQgetvalue(res, 0, 0), "t") == 0);
	PQclear(res);
	return answer;
}



/*
 * do_edit -- handler for \e
 *
 * If you do not specify a filename, the current query buffer will be copied
 * into a temporary one.
 */

static bool
editFile(const char *fname)
{
	const char *editorName;
	char	   *sys;
	int			result;

#ifdef USE_ASSERT_CHECKING
	assert(fname);
#else
	if (!fname)
		return false;
#endif

	/* Find an editor to use */
	editorName = getenv("PSQL_EDITOR");
	if (!editorName)
		editorName = getenv("EDITOR");
	if (!editorName)
		editorName = getenv("VISUAL");
	if (!editorName)
		editorName = DEFAULT_EDITOR;

	sys = malloc(strlen(editorName) + strlen(fname) + 32 + 1);
	if (!sys)
		return false;
	sprintf(sys, "exec %s %s", editorName, fname);
	result = system(sys);
	if (result == -1)
		psql_error("could not start editor %s\n", editorName);
	else if (result == 127)
		psql_error("could not start /bin/sh\n");
	free(sys);

	return result == 0;
}


/* call this one */
static bool
do_edit(const char *filename_arg, PQExpBuffer query_buf)
{
	char		fnametmp[MAXPGPATH];
	FILE	   *stream = NULL;
	const char *fname;
	bool		error = false;
	int			fd;

#ifndef WIN32
	struct stat before,
				after;

#endif

	if (filename_arg)
		fname = filename_arg;

	else
	{
		/* make a temp file to edit */
#ifndef WIN32
		const char *tmpdirenv = getenv("TMPDIR");

		sprintf(fnametmp, "%s/psql.edit.%ld.%ld",
				tmpdirenv ? tmpdirenv : "/tmp",
				(long) geteuid(), (long) getpid());
#else
		GetTempFileName(".", "psql", 0, fnametmp);
#endif
		fname = (const char *) fnametmp;

		fd = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
		if (fd != -1)
			stream = fdopen(fd, "w");

		if (fd == -1 || !stream)
		{
			psql_error("could not open temporary file %s: %s\n", fname, strerror(errno));
			error = true;
		}
		else
		{
			unsigned int ql = query_buf->len;

			if (ql == 0 || query_buf->data[ql - 1] != '\n')
			{
				appendPQExpBufferChar(query_buf, '\n');
				ql++;
			}

			if (fwrite(query_buf->data, 1, ql, stream) != ql)
			{
				psql_error("%s: %s\n", fname, strerror(errno));
				fclose(stream);
				remove(fname);
				error = true;
			}
			else
				fclose(stream);
		}
	}

#ifndef WIN32
	if (!error && stat(fname, &before) != 0)
	{
		psql_error("%s: %s\n", fname, strerror(errno));
		error = true;
	}
#endif

	/* call editor */
	if (!error)
		error = !editFile(fname);

#ifndef WIN32
	if (!error && stat(fname, &after) != 0)
	{
		psql_error("%s: %s\n", fname, strerror(errno));
		error = true;
	}

	if (!error && before.st_mtime != after.st_mtime)
	{
#else
	if (!error)
	{
#endif
		stream = fopen(fname, "r");
		if (!stream)
		{
			psql_error("%s: %s\n", fname, strerror(errno));
			error = true;
		}
		else
		{
			/* read file back in */
			char		line[1024];

			resetPQExpBuffer(query_buf);
			while (fgets(line, sizeof(line), stream) != NULL)
				appendPQExpBufferStr(query_buf, line);

			if (ferror(stream))
			{
				psql_error("%s: %s\n", fname, strerror(errno));
				error = true;
			}

			fclose(stream);
		}

	}

	/* remove temp file */
	if (!filename_arg)
	{
		if (remove(fname) == -1)
		{
			psql_error("%s: %s\n", fname, strerror(errno));
			error = true;
		}
	}

	return !error;
}



/*
 * process_file
 *
 * Read commands from filename and then them to the main processing loop
 * Handler for \i, but can be used for other things as well.
 */
int
process_file(char *filename)
{
	FILE	   *fd;
	int			result;
	char	   *oldfilename;

	if (!filename)
		return false;

	fd = fopen(filename, "r");

	if (!fd)
	{
		psql_error("%s: %s\n", filename, strerror(errno));
		return false;
	}

	oldfilename = pset.inputfile;
	pset.inputfile = filename;
	result = MainLoop(fd);
	fclose(fd);
	pset.inputfile = oldfilename;
	return result;
}



/*
 * do_pset
 *
 */
static const char *
_align2string(enum printFormat in)
{
	switch (in)
	{
			case PRINT_NOTHING:
			return "nothing";
			break;
		case PRINT_UNALIGNED:
			return "unaligned";
			break;
		case PRINT_ALIGNED:
			return "aligned";
			break;
		case PRINT_HTML:
			return "html";
			break;
		case PRINT_LATEX:
			return "latex";
			break;
	}
	return "unknown";
}


bool
do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
{
	size_t		vallen = 0;

#ifdef USE_ASSERT_CHECKING
	assert(param);
#else
	if (!param)
		return false;
#endif

	if (value)
		vallen = strlen(value);

	/* set format */
	if (strcmp(param, "format") == 0)
	{
		if (!value)
			;
		else if (strncasecmp("unaligned", value, vallen) == 0)
			popt->topt.format = PRINT_UNALIGNED;
		else if (strncasecmp("aligned", value, vallen) == 0)
			popt->topt.format = PRINT_ALIGNED;
		else if (strncasecmp("html", value, vallen) == 0)
			popt->topt.format = PRINT_HTML;
		else if (strncasecmp("latex", value, vallen) == 0)
			popt->topt.format = PRINT_LATEX;
		else
		{
			psql_error("\\pset: allowed formats are unaligned, aligned, html, latex\n");
			return false;
		}

		if (!quiet)
			printf(gettext("Output format is %s.\n"), _align2string(popt->topt.format));
	}

	/* set border style/width */
	else if (strcmp(param, "border") == 0)
	{
		if (value)
			popt->topt.border = atoi(value);

		if (!quiet)
			printf(gettext("Border style is %d.\n"), popt->topt.border);
	}

	/* set expanded/vertical mode */
	else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0)
	{
		popt->topt.expanded = !popt->topt.expanded;
		if (!quiet)
			printf(popt->topt.expanded
				   ? gettext("Expanded display is on.\n")
				   : gettext("Expanded display is off.\n"));
	}

	/* null display */
	else if (strcmp(param, "null") == 0)
	{
		if (value)
		{
			free(popt->nullPrint);
			popt->nullPrint = xstrdup(value);
		}
		if (!quiet)
			printf(gettext("Null display is '%s'.\n"), popt->nullPrint ? popt->nullPrint : "");
	}

	/* field separator for unaligned text */
	else if (strcmp(param, "fieldsep") == 0)
	{
		if (value)
		{
			free(popt->topt.fieldSep);
			popt->topt.fieldSep = xstrdup(value);
		}
		if (!quiet)
			printf(gettext("Field separator is '%s'.\n"), popt->topt.fieldSep);
	}

	/* record separator for unaligned text */
	else if (strcmp(param, "recordsep") == 0)
	{
		if (value)
		{
			free(popt->topt.recordSep);
			popt->topt.recordSep = xstrdup(value);
		}
		if (!quiet)
		{
			if (strcmp(popt->topt.recordSep, "\n") == 0)
				printf(gettext("Record separator is <newline>."));
			else
				printf(gettext("Record separator is '%s'.\n"), popt->topt.recordSep);
		}
	}

	/* toggle between full and barebones format */
	else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
	{
		popt->topt.tuples_only = !popt->topt.tuples_only;
		if (!quiet)
		{
			if (popt->topt.tuples_only)
				puts(gettext("Showing only tuples."));
			else
				puts(gettext("Tuples only is off."));
		}
	}

	/* set title override */
	else if (strcmp(param, "title") == 0)
	{
		free(popt->title);
		if (!value)
			popt->title = NULL;
		else
			popt->title = xstrdup(value);

		if (!quiet)
		{
			if (popt->title)
				printf(gettext("Title is \"%s\".\n"), popt->title);
			else
				printf(gettext("Title is unset.\n"));
		}
	}

	/* set HTML table tag options */
	else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0)
	{
		free(popt->topt.tableAttr);
		if (!value)
			popt->topt.tableAttr = NULL;
		else
			popt->topt.tableAttr = xstrdup(value);

		if (!quiet)
		{
			if (popt->topt.tableAttr)
				printf(gettext("Table attribute is \"%s\".\n"), popt->topt.tableAttr);
			else
				printf(gettext("Table attributes unset.\n"));
		}
	}

	/* toggle use of pager */
	else if (strcmp(param, "pager") == 0)
	{
		popt->topt.pager = !popt->topt.pager;
		if (!quiet)
		{
			if (popt->topt.pager)
				puts(gettext("Using pager is on."));
			else
				puts(gettext("Using pager is off."));
		}
	}

	/* disable "(x rows)" footer */
	else if (strcmp(param, "footer") == 0)
	{
		popt->default_footer = !popt->default_footer;
		if (!quiet)
		{
			if (popt->default_footer)
				puts(gettext("Default footer is on."));
			else
				puts(gettext("Default footer is off."));
		}
	}

	else
	{
		psql_error("\\pset: unknown option: %s\n", param);
		return false;
	}

	return true;
}



#define DEFAULT_SHELL "/bin/sh"

static bool
do_shell(const char *command)
{
	int			result;

	if (!command)
	{
		char	   *sys;
		const char *shellName;

		shellName = getenv("SHELL");
		if (shellName == NULL)
			shellName = DEFAULT_SHELL;

		sys = malloc(strlen(shellName) + 16);
		if (!sys)
		{
			psql_error("out of memory\n");
			if (pset.cur_cmd_interactive)
				return false;
			else
				exit(EXIT_FAILURE);
		}
		sprintf(sys, "exec %s", shellName);
		result = system(sys);
		free(sys);
	}
	else
		result = system(command);

	if (result == 127 || result == -1)
	{
		psql_error("\\!: failed\n");
		return false;
	}
	return true;
}
