/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright (c) 2000-2003, PostgreSQL Global Development Group
 *
 * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.113 2004/02/19 19:40:08 tgl Exp $
 */
#include "postgres_fe.h"
#include "command.h"

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

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

#include "common.h"
#include "copy.h"
#include "describe.h"
#include "help.h"
#include "input.h"
#include "large_obj.h"
#include "mainloop.h"
#include "print.h"
#include "psqlscan.h"
#include "settings.h"
#include "variables.h"
#include "mb/pg_wchar.h"


/* functions for use in this file */
static backslashResult exec_command(const char *cmd,
									PsqlScanState scan_state,
									PQExpBuffer query_buf);
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().
 *
 * scan_state is a lexer working state that is set to continue scanning
 * just after the '\'.  The lexer is advanced past the command and all
 * arguments on return.
 *
 * 'query_buf' contains the query-so-far, which may be modified by
 * execution of the backslash command (for example, \r clears it).
 * query_buf can be NULL if there is no query so far.
 *
 * Returns a status code indicating what action is desired, see command.h.
 *----------
 */

backslashResult
HandleSlashCmds(PsqlScanState scan_state,
				PQExpBuffer query_buf)
{
	backslashResult status = CMD_SKIP_LINE;
	char	   *cmd;
	char	   *arg;

	psql_assert(scan_state);

	/* Parse off the command name */
	cmd = psql_scan_slash_command(scan_state);

	/* And try to execute it */
	status = exec_command(cmd, scan_state, query_buf);

	if (status == CMD_UNKNOWN && strlen(cmd) > 1)
	{
		/*
		 * 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];

		/* don't change cmd until we know it's okay */
		new_cmd[0] = cmd[0];
		new_cmd[1] = '\0';

		psql_scan_slash_pushback(scan_state, cmd + 1);

		status = exec_command(new_cmd, scan_state, query_buf);

		if (status != CMD_UNKNOWN)
		{
			/* adjust cmd for possible messages below */
			cmd[1] = '\0';

#if 0							/* turned out to be too annoying */
			if (isalpha((unsigned char) 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"), cmd);
		else
			psql_error("invalid command \\%s\n", cmd);
		status = CMD_ERROR;
	}

	/* eat the rest of the options, if any */
	while ((arg = psql_scan_slash_option(scan_state,
										 OT_NORMAL, NULL, false)))
	{
		if (status != CMD_ERROR)
			psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
		free(arg);
	}

	/* if there is a trailing \\, swallow it */
	psql_scan_slash_command_end(scan_state);

	free(cmd);

	return status;
}

/*
 * Subroutine to actually try to execute a backslash command.
 */
static backslashResult
exec_command(const char *cmd,
			 PsqlScanState scan_state,
			 PQExpBuffer query_buf)
{
	bool		success = true; /* indicate here if the command ran ok or
								 * failed */
	bool		quiet = QUIET();
	backslashResult status = CMD_SKIP_LINE;

	/*
	 * \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 = psql_scan_slash_option(scan_state,
												 OT_NORMAL, NULL, true);

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

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

		/*
		 * Ideally we should treat the arguments as SQL identifiers.  But
		 * for backwards compatibility with 7.2 and older pg_dump files,
		 * we have to take unquoted arguments verbatim (don't downcase
		 * them). For now, double-quoted arguments may be stripped of
		 * double quotes (as if SQL identifiers).  By 7.4 or so, pg_dump
		 * files can be expected to double-quote all mixed-case \connect
		 * arguments, and then we can get rid of OT_SQLIDHACK.
		 */
		opt1 = psql_scan_slash_option(scan_state,
									  OT_SQLIDHACK, &opt1q, true);
		opt2 = psql_scan_slash_option(scan_state,
									  OT_SQLIDHACK, &opt2q, true);

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

		free(opt1);
		free(opt2);
	}

	/* \cd */
	else if (strcmp(cmd, "cd") == 0)
	{
		char	   *opt = psql_scan_slash_option(scan_state,
												 OT_NORMAL, NULL, true);
		char	   *dir;

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

			pw = getpwuid(geteuid());
			if (!pw)
			{
				psql_error("could not get home directory: %s\n", strerror(errno));
				exit(EXIT_FAILURE);
			}
			dir = pw->pw_dir;
#else							/* WIN32 */

			/*
			 * On Windows, 'cd' without arguments prints the current
			 * directory, so if someone wants to code this here instead...
			 */
			dir = "/";
#endif   /* WIN32 */
		}

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

		if (opt)
			free(opt);
	}

	/* \copy */
	else if (strcasecmp(cmd, "copy") == 0)
	{
		char	   *opt = psql_scan_slash_option(scan_state,
												 OT_WHOLE_LINE, NULL, false);

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

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

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

		/* We don't do SQLID reduction on the pattern yet */
		pattern = psql_scan_slash_option(scan_state,
										 OT_NORMAL, NULL, true);

		show_verbose = strchr(cmd, '+') ? true : false;

		switch (cmd[1])
		{
			case '\0':
			case '+':
				if (pattern)
					success = describeTableDetails(pattern, show_verbose);
				else
					/* standard listing of interesting things */
					success = listTables("tvs", NULL, show_verbose);
				break;
			case 'a':
				success = describeAggregates(pattern, show_verbose);
				break;
			case 'c':
				success = listConversions(pattern);
				break;
			case 'C':
				success = listCasts(pattern);
				break;
			case 'd':
				success = objectDescription(pattern);
				break;
			case 'D':
				success = listDomains(pattern);
				break;
			case 'f':
				success = describeFunctions(pattern, show_verbose);
				break;
			case 'g':
				success = describeGroups(pattern);
				break;
			case 'l':
				success = do_lo_list();
				break;
			case 'n':
				success = listSchemas(pattern);
				break;
			case 'o':
				success = describeOperators(pattern);
				break;
			case 'p':
				success = permissionsList(pattern);
				break;
			case 'T':
				success = describeTypes(pattern, show_verbose);
				break;
			case 't':
			case 'v':
			case 'i':
			case 's':
			case 'S':
				success = listTables(&cmd[1], pattern, show_verbose);
				break;
			case 'u':
				success = describeUsers(pattern);
				break;

			default:
				status = CMD_UNKNOWN;
		}

		if (pattern)
			free(pattern);
	}


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

		if (!query_buf)
		{
			psql_error("no query buffer\n");
			status = CMD_ERROR;
		}
		else
		{
			fname = psql_scan_slash_option(scan_state,
										   OT_NORMAL, NULL, true);
			expand_tilde(&fname);
			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 = psql_scan_slash_option(scan_state,
											   OT_NORMAL, &quoted, false)))
		{
			if (!quoted && strcmp(value, "-n") == 0)
				no_newline = true;
			else
			{
				if (first)
					first = false;
				else
					fputc(' ', fout);
				fputs(value, fout);
			}
			free(value);
		}
		if (!no_newline)
			fputs("\n", fout);
	}

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

		if (!encoding)
		{
			/* show encoding */
			puts(pg_encoding_to_char(pset.encoding));
		}
		else
		{
			/* set encoding */
			if (PQsetClientEncoding(pset.db, encoding) == -1)
				psql_error("%s: invalid encoding name or conversion procedure not found\n", encoding);
			else
			{
				/* save encoding info into psql internal data */
				pset.encoding = PQclientEncoding(pset.db);
				pset.popt.topt.encoding = pset.encoding;
				SetVariable(pset.vars, "ENCODING",
							pg_encoding_to_char(pset.encoding));
			}
			free(encoding);
		}
	}

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

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

	/* \g means send query */
	else if (strcmp(cmd, "g") == 0)
	{
		char	   *fname = psql_scan_slash_option(scan_state,
												   OT_FILEPIPE, NULL, false);

		if (!fname)
			pset.gfname = NULL;
		else
		{
			expand_tilde(&fname);
			pset.gfname = pg_strdup(fname);
		}
		free(fname);
		status = CMD_SEND;
	}

	/* help */
	else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
	{
		char	   *opt = psql_scan_slash_option(scan_state,
												 OT_WHOLE_LINE, NULL, false);

		helpSQL(opt, pset.popt.topt.pager);
		free(opt);
	}

	/* 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 = psql_scan_slash_option(scan_state,
												   OT_NORMAL, NULL, true);

		if (!fname)
		{
			psql_error("\\%s: missing required argument\n", cmd);
			success = false;
		}
		else
		{
			expand_tilde(&fname);
			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 = psql_scan_slash_option(scan_state,
									  OT_NORMAL, NULL, true);
		opt2 = psql_scan_slash_option(scan_state,
									  OT_NORMAL, NULL, true);

		if (strcmp(cmd + 3, "export") == 0)
		{
			if (!opt2)
			{
				psql_error("\\%s: missing required argument\n", cmd);
				success = false;
			}
			else
			{
				expand_tilde(&opt2);
				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
			{
				expand_tilde(&opt1);
				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 = psql_scan_slash_option(scan_state,
												   OT_FILEPIPE, NULL, true);

		expand_tilde(&fname);
		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 = psql_scan_slash_option(scan_state,
												  OT_NORMAL, NULL, false);
		char	   *opt1 = psql_scan_slash_option(scan_state,
												  OT_NORMAL, NULL, false);

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

		free(opt0);
		free(opt1);
	}

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

	/* reset(clear) the buffer */
	else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
	{
		resetPQExpBuffer(query_buf);
		psql_scan_reset(scan_state);
		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 = psql_scan_slash_option(scan_state,
												   OT_NORMAL, NULL, true);

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

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

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

		if (!opt0)
		{
			/* list all variables */
			PrintVariables(pset.vars);
			success = true;
		}
		else
		{
			/*
			 * Set variable to the concatenation of the arguments.
			 */
			char	   *newval;
			char	   *opt;

			opt = psql_scan_slash_option(scan_state,
										 OT_NORMAL, NULL, false);
			newval = pg_strdup(opt ? opt : "");
			free(opt);

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

			if (SetVariable(pset.vars, opt0, newval))
			{
				/* Check for special variables */
				if (strcmp(opt0, "VERBOSITY") == 0)
					SyncVerbosityVariable();
			}
			else
			{
				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 = psql_scan_slash_option(scan_state,
												   OT_NORMAL, NULL, false);

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

	/* \timing -- toggle timing of queries */
	else if (strcmp(cmd, "timing") == 0)
	{
		pset.timing = !pset.timing;
		if (!quiet)
		{
			if (pset.timing)
				puts(gettext("Timing is on."));
			else
				puts(gettext("Timing is off."));
		}
	}

	/* \unset */
	else if (strcmp(cmd, "unset") == 0)
	{
		char	   *opt = psql_scan_slash_option(scan_state,
												 OT_NORMAL, NULL, false);

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

	/* \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 = psql_scan_slash_option(scan_state,
										   OT_FILEPIPE, NULL, true);
			expand_tilde(&fname);

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

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

		if (fd)
		{
			int			result;

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

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

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

		free(fname);
	}

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


	/* \z -- list table rights (equivalent to \dp) */
	else if (strcmp(cmd, "z") == 0)
	{
		char	   *pattern = psql_scan_slash_option(scan_state,
													 OT_NORMAL, NULL, true);

		success = permissionsList(pattern);
		if (pattern)
			free(pattern);
	}

	/* \! -- shell escape */
	else if (strcmp(cmd, "!") == 0)
	{
		char	   *opt = psql_scan_slash_option(scan_state,
												 OT_WHOLE_LINE, NULL, false);

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

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

#if 0

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

		while ((value = psql_scan_slash_option(scan_state,
											   OT_NORMAL, NULL, true)))
		{
			fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value);
			free(value);
		}
	}
#endif

	else
		status = CMD_UNKNOWN;

	if (!success)
		status = CMD_ERROR;

	return status;
}



/* 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) */
	UnsyncVariables();

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

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

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

	/*
	 * Use old password (if any) if no new one given and we are
	 * reconnecting as same user
	 */
	if (!pwparam && oldconn && PQuser(oldconn) && userparam &&
		strcmp(PQuser(oldconn), userparam) == 0)
		pwparam = PQpass(oldconn);

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

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

	free(prompted_password);

	/*
	 * If connection failed, try at least keep the old one. That's
	 * probably more convenient than just kicking you out of the program.
	 */
	if (!pset.db || PQstatus(pset.db) == CONNECTION_BAD)
	{
		if (pset.cur_cmd_interactive)
		{
			psql_error("%s", PQerrorMessage(pset.db));
			PQfinish(pset.db);
			if (oldconn)
			{
				fputs(gettext("Previous connection kept\n"), stderr);
				pset.db = oldconn;
			}
			else
				pset.db = NULL;
		}
		else
		{
			/*
			 * we don't want unpredictable things to happen in scripting
			 * mode
			 */
			psql_error("\\connect: %s", PQerrorMessage(pset.db));
			PQfinish(pset.db);
			if (oldconn)
				PQfinish(oldconn);
			pset.db = NULL;
		}
	}
	else
	{
		if (!QUIET())
		{
			if (userparam != new_user)	/* no new user */
				printf(gettext("You are now connected to database \"%s\".\n"), dbparam);
			else if (dbparam != new_dbname)		/* no new db */
				printf(gettext("You are now connected as new user \"%s\".\n"), new_user);
			else
/* both new */
				printf(gettext("You are now connected to database \"%s\" as user \"%s\".\n"),
					   PQdb(pset.db), PQuser(pset.db));
		}

		if (oldconn)
			PQfinish(oldconn);

		success = true;
	}

	PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);

	/* Update variables */
	SyncVariables();

	return success;
}


/*
 * SyncVariables
 *
 * Make psql's internal variables agree with connection state upon
 * establishing a new connection.
 */
void
SyncVariables(void)
{
	/* get stuff from connection */
	pset.encoding = PQclientEncoding(pset.db);
	pset.popt.topt.encoding = pset.encoding;

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

	/* send stuff to it, too */
	SyncVerbosityVariable();
}

/*
 * UnsyncVariables
 *
 * Clear variables that should be not be set when there is no connection.
 */
void
UnsyncVariables(void)
{
	SetVariable(pset.vars, "DBNAME", NULL);
	SetVariable(pset.vars, "USER", NULL);
	SetVariable(pset.vars, "HOST", NULL);
	SetVariable(pset.vars, "PORT", NULL);
	SetVariable(pset.vars, "ENCODING", NULL);
}

/*
 * Update connection state from VERBOSITY variable
 */
void
SyncVerbosityVariable(void)
{
	switch (SwitchVariable(pset.vars, "VERBOSITY",
						   "default", "terse", "verbose", NULL))
	{
		case 1:			/* default */
			PQsetErrorVerbosity(pset.db, PQERRORS_DEFAULT);
			break;
		case 2:			/* terse */
			PQsetErrorVerbosity(pset.db, PQERRORS_TERSE);
			break;
		case 3:			/* verbose */
			PQsetErrorVerbosity(pset.db, PQERRORS_VERBOSE);
			break;
		default:				/* not set or unrecognized value */
			PQsetErrorVerbosity(pset.db, PQERRORS_DEFAULT);
			break;
	}
}


/*
 * 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;

	psql_assert(fname);

	/* 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 = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
	sprintf(sys,
#ifndef WIN32
			"exec "
#endif
			"%s '%s'", editorName, fname);
	result = system(sys);
	if (result == -1)
		psql_error("could not start editor \"%s\"\n", editorName);
	else if (result == 127)
		psql_error("could not start /bin/sh\n");
	free(sys);

	return result == 0;
}


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

#ifndef WIN32
	struct stat before,
				after;
#endif

	if (filename_arg)
		fname = filename_arg;

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

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

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

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

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

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

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

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

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

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

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

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

#ifdef USE_READLINE
#ifdef HAVE_REPLACE_HISTORY_ENTRY

			replace_history_entry(where_history(), query_buf->data, NULL);
#else
			add_history(query_buf->data);
#endif
#endif
			fclose(stream);
		}

	}

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

	return !error;
}



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

	if (!filename)
		return false;

	fd = fopen(filename, "r");

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

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



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


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

	psql_assert(param);

	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 = pg_strdup(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 = pg_strdup(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 = pg_strdup(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 = pg_strdup(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 = pg_strdup(value);

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

	/* toggle use of pager */
	else if (strcmp(param, "pager") == 0)
	{
		if (value && strcasecmp(value, "always") == 0)
			popt->topt.pager = 2;
		else if (popt->topt.pager == 1)
			popt->topt.pager = 0;
		else
			popt->topt.pager = 1;
		if (!quiet)
		{
			if (popt->topt.pager == 1)
				puts(gettext("Pager is used for long output."));
			else if (popt->topt.pager == 2)
				puts(gettext("Pager is always used."));
			else
				puts(gettext("Pager usage 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 = pg_malloc(strlen(shellName) + 16);
		sprintf(sys,
#ifndef WIN32
				"exec "
#endif
				"%s", shellName);
		result = system(sys);
		free(sys);
	}
	else
		result = system(command);

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