Skip to content
Snippets Groups Projects
command.c 39.91 KiB
/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright 2000 by PostgreSQL Global Development Group
 *
 * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.21 2000/02/20 02:37:40 tgl Exp $
 */
#include "postgres.h"
#include "command.h"

#include <errno.h>
#include <assert.h>
#include <ctype.h>
#ifndef WIN32
#include <sys/types.h>			/* for umask() */
#include <sys/stat.h>			/* for umask(), stat() */
#include <unistd.h>				/* for geteuid(), getpid(), stat() */
#else
#include <win32.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 "miscadmin.h"
#include "mb/pg_wchar.h"
#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 };
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);
    assert(end_of_cmd);
#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\\");

    if (my_line[blank_loc] == '\\')
    {
        continue_parse = &my_line[blank_loc];
		my_line[blank_loc] = '\0';
    }
	/* 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 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.
		 */
		char		new_cmd[2];

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

		status = exec_command(new_cmd, my_line + 1, &continue_parse, query_buf);

        if (status != CMD_UNKNOWN && isalpha(new_cmd[0]))
            psql_error("Warning: this syntax is deprecated\n");
	}

	if (status == CMD_UNKNOWN)
	{
        if (pset.cur_cmd_interactive)
            fprintf(stderr, "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 (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;

    /*
     * 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_NORMAL, &opt1q);
        opt2 = scan_option(&string, OT_NORMAL, &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);
	}

	/* \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':
				if (cmd[1] == 'S' && cmd[2] == '\0')
					success = listTables("Stvs", NULL, show_verbose);
				else
					success = listTables(&cmd[1], name, show_verbose);
				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);
	}

#ifdef MULTIBYTE
	/* \eset -- set client side encoding */
	else if (strcmp(cmd, "eset") == 0)
	{
		char *encoding = scan_option(&string, OT_NORMAL, NULL);
		if (PQsetClientEncoding(pset.db, encoding) == -1)
		{
			psql_error("\\%s: invalid encoding\n", cmd);
		}
		/* save encoding info into psql internal data */
		pset.encoding = PQclientEncoding(pset.db);
		free(encoding);
	}
	/* \eshow -- show encoding info */
	else if (strcmp(cmd, "eshow") == 0)
	{
		int encoding = PQclientEncoding(pset.db);
		if (encoding == -1)
		{
			psql_error("\\%s: there is no connection\n", cmd);
		}
		printf("%s\n", pg_encoding_to_char(encoding));
	}
#endif
	/* \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_NORMAL, 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")] : 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);
            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_NORMAL, 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("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("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("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_NORMAL, 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 1
    /*
	 * 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(scan_option(&string, OT_NORMAL, NULL)) ;

    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");

    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 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 (type == OT_NORMAL)
            {
                strncpy(return_val, &options_string[pos], jj-pos+1);
                return_val[jj-pos+1] = '\0';
            }
            /*
             * If this is expected to be an SQL identifier like option
             * then we strip out the double quotes
             */
            else 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';
            }       
                    
            *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 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 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");
            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;

        /*
         * A normal word
         */
        default:
        {
            size_t token_end;
            char * cp;
            
            token_end = strcspn(&options_string[pos], " \t");
            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 (isascii(*cp))
                        *cp = tolower(*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
		{
			*tmp++ = *p;
			esc = false;
		}
	}

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



/* do_connect
 * -- handler for \connect
 *
 * Connects to a database (new_dbname) as a certain user (new_user).
 * The new user can be NULL. A db name of "-" is the same as the old one.
 * (That is, the one currently in pset. But pset.db can also be NULL. A NULL
 * dbname is handled by libpq.)
 * Returns true if all ok, false if the new connection couldn't be established
 * but the old one was set back. Otherwise it terminates the program.
 */
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);

	/* 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)
		{
			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("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("You are now connected to database %s.\n", dbparam);
			else if (dbparam != new_dbname)		/* no new db */
				printf("You are now connected as new user %s.\n", new_user);
			else /* both new */
				printf("You are now connected to database %s as user %s.\n",
					   PQdb(pset.db), PQuser(pset.db));
		}

		if (oldconn)
			PQfinish(oldconn);

		success = true;
	}

    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));

    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;
	const char *fname;
	bool		error = false;

#ifndef WIN32
	struct stat before,
				after;

#endif

	if (filename_arg)
		fname = filename_arg;

	else
	{
		/* make a temp file to edit */
#ifndef WIN32
		mode_t		oldumask;
        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;

#ifndef WIN32
		oldumask = umask(0177);
#endif
		stream = fopen(fname, "w");
#ifndef WIN32
		umask(oldumask);
#endif

		if (!stream)
		{
            psql_error("couldn't open temp 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];
			size_t		result;

			resetPQExpBuffer(query_buf);
			do
			{
				result = fread(line, 1, 1024, stream);
				if (ferror(stream))
				{
                    psql_error("%s: %s\n", fname, strerror(errno));
					error = true;
					break;
				}
				appendBinaryPQExpBuffer(query_buf, line, result);
			} while (!feof(stream));
			appendPQExpBufferChar(query_buf, '\0');

			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.
 */
bool
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 == EXIT_SUCCESS);
}



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


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

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

	if (value)
		vallen = strlen(value);

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

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

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

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

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

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

	/* field separator for unaligned text */
	else if (strcmp(param, "fieldsep") == 0)
	{
		if (value)
		{
			free(popt->topt.fieldSep);
			popt->topt.fieldSep = xstrdup(value);
		}
		if (!quiet)
			printf("Field separator is '%s'.\n", popt->topt.fieldSep);
	}
	/* 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("Record separator is <newline>.");
            else
                printf("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("Showing only tuples.");
			else
				puts("Tuples only is off.");
		}
	}

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

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

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

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

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


	else
	{
		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;
}