/src/bin/psql/command.c
C | 2943 lines | 2148 code | 374 blank | 421 comment | 736 complexity | bfd1f6797298e701f4587872bb8a4ac9 MD5 | raw file
Possible License(s): AGPL-3.0
Large files files are truncated, but you can click here to view the full file
- /*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2014, PostgreSQL Global Development Group
- *
- * src/bin/psql/command.c
- */
- #include "postgres_fe.h"
- #include "command.h"
- #ifdef __BORLANDC__ /* needed for BCC */
- #undef mkdir
- #endif
- #include <ctype.h>
- #include <time.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>
- #include <sys/types.h> /* for umask() */
- #include <sys/stat.h> /* for stat() */
- #endif
- #ifdef USE_OPENSSL
- #include <openssl/ssl.h>
- #endif
- #include "portability/instr_time.h"
- #include "libpq-fe.h"
- #include "pqexpbuffer.h"
- #include "dumputils.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"
- /* 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,
- int lineno, bool *edited);
- static bool do_connect(char *dbname, char *user, char *host, char *port);
- static bool do_shell(const char *command);
- static bool do_watch(PQExpBuffer query_buf, long sleep);
- static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
- static bool get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf);
- static int strip_lineno_from_funcdesc(char *func);
- static void minimal_error_message(PGresult *res);
- static void printSSLInfo(void);
- static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
- #ifdef WIN32
- static void checkWin32Codepage(void);
- #endif
- /*----------
- * 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 = PSQL_CMD_SKIP_LINE;
- char *cmd;
- char *arg;
- Assert(scan_state != NULL);
- /* 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 == PSQL_CMD_UNKNOWN)
- {
- if (pset.cur_cmd_interactive)
- psql_error("Invalid command \\%s. Try \\? for help.\n", cmd);
- else
- psql_error("invalid command \\%s\n", cmd);
- status = PSQL_CMD_ERROR;
- }
- if (status != PSQL_CMD_ERROR)
- {
- /* eat any remaining arguments after a valid command */
- /* note we suppress evaluation of backticks here */
- while ((arg = psql_scan_slash_option(scan_state,
- OT_NO_EVAL, NULL, false)))
- {
- psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
- free(arg);
- }
- }
- else
- {
- /* silently throw away rest of line after an erroneous command */
- while ((arg = psql_scan_slash_option(scan_state,
- OT_WHOLE_LINE, NULL, false)))
- free(arg);
- }
- /* if there is a trailing \\, swallow it */
- psql_scan_slash_command_end(scan_state);
- free(cmd);
- /* some commands write to queryFout, so make sure output is sent */
- fflush(pset.queryFout);
- return status;
- }
- /*
- * Read and interpret an argument to the \connect slash command.
- */
- static char *
- read_connect_arg(PsqlScanState scan_state)
- {
- char *result;
- char quote;
- /*
- * 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.
- */
- result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, "e, true);
- if (!result)
- return NULL;
- if (quote)
- return result;
- if (*result == '\0' || strcmp(result, "-") == 0)
- return NULL;
- return result;
- }
- /*
- * 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 */
- backslashResult status = PSQL_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, pset.quiet);
- else
- success = do_pset("format", "unaligned", &pset.popt, pset.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, pset.quiet);
- free(opt);
- }
- /*
- * \c or \connect -- connect to database using the specified parameters.
- *
- * \c dbname user host port
- *
- * If any of these parameters are omitted or specified as '-', the current
- * value of the parameter will be used instead. If the parameter has no
- * current value, the default value for that parameter will be used. Some
- * examples:
- *
- * \c - - hst Connect to current database on current port of host
- * "hst" as current user. \c - usr - prt Connect to current database on
- * "prt" port of current host as user "usr". \c dbs Connect to
- * "dbs" database on current port of current host as current user.
- */
- else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
- {
- char *opt1,
- *opt2,
- *opt3,
- *opt4;
- opt1 = read_connect_arg(scan_state);
- opt2 = read_connect_arg(scan_state);
- opt3 = read_connect_arg(scan_state);
- opt4 = read_connect_arg(scan_state);
- success = do_connect(opt1, opt2, opt3, opt4);
- free(opt1);
- free(opt2);
- free(opt3);
- free(opt4);
- }
- /* \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;
- uid_t user_id = geteuid();
- errno = 0; /* clear errno before call */
- pw = getpwuid(user_id);
- if (!pw)
- {
- psql_error("could not get home directory for user id %ld: %s\n",
- (long) user_id,
- errno ? strerror(errno) : _("user does not exist"));
- 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);
- }
- /* \conninfo -- display information about the current connection */
- else if (strcmp(cmd, "conninfo") == 0)
- {
- char *db = PQdb(pset.db);
- char *host = (PQhostaddr(pset.db) != NULL) ? PQhostaddr(pset.db) : PQhost(pset.db);
- if (db == NULL)
- printf(_("You are currently not connected to a database.\n"));
- else
- {
- if (host == NULL)
- host = DEFAULT_PGSOCKET_DIR;
- /* If the host is an absolute path, the connection is via socket */
- if (is_absolute_path(host))
- printf(_("You are connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
- db, PQuser(pset.db), host, PQport(pset.db));
- else
- printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
- db, PQuser(pset.db), host, PQport(pset.db));
- printSSLInfo();
- }
- }
- /* \copy */
- else if (pg_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,
- show_system;
- /* 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;
- show_system = strchr(cmd, 'S') ? true : false;
- switch (cmd[1])
- {
- case '\0':
- case '+':
- case 'S':
- if (pattern)
- success = describeTableDetails(pattern, show_verbose, show_system);
- else
- /* standard listing of interesting things */
- success = listTables("tvmsE", NULL, show_verbose, show_system);
- break;
- case 'a':
- success = describeAggregates(pattern, show_verbose, show_system);
- break;
- case 'b':
- success = describeTablespaces(pattern, show_verbose);
- break;
- case 'c':
- success = listConversions(pattern, show_verbose, show_system);
- break;
- case 'C':
- success = listCasts(pattern, show_verbose);
- break;
- case 'd':
- if (strncmp(cmd, "ddp", 3) == 0)
- success = listDefaultACLs(pattern);
- else
- success = objectDescription(pattern, show_system);
- break;
- case 'D':
- success = listDomains(pattern, show_verbose, show_system);
- break;
- case 'f': /* function subsystem */
- switch (cmd[2])
- {
- case '\0':
- case '+':
- case 'S':
- case 'a':
- case 'n':
- case 't':
- case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
- break;
- default:
- status = PSQL_CMD_UNKNOWN;
- break;
- }
- break;
- case 'g':
- /* no longer distinct from \du */
- success = describeRoles(pattern, show_verbose);
- break;
- case 'l':
- success = do_lo_list();
- break;
- case 'L':
- success = listLanguages(pattern, show_verbose, show_system);
- break;
- case 'n':
- success = listSchemas(pattern, show_verbose, show_system);
- break;
- case 'o':
- success = describeOperators(pattern, show_verbose, show_system);
- break;
- case 'O':
- success = listCollations(pattern, show_verbose, show_system);
- break;
- case 'p':
- success = permissionsList(pattern);
- break;
- case 'T':
- success = describeTypes(pattern, show_verbose, show_system);
- break;
- case 't':
- case 'v':
- case 'm':
- case 'i':
- case 's':
- case 'E':
- success = listTables(&cmd[1], pattern, show_verbose, show_system);
- break;
- case 'r':
- if (cmd[2] == 'd' && cmd[3] == 's')
- {
- char *pattern2 = NULL;
- if (pattern)
- pattern2 = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- success = listDbRoleSettings(pattern, pattern2);
- }
- else
- success = PSQL_CMD_UNKNOWN;
- break;
- case 'u':
- success = describeRoles(pattern, show_verbose);
- break;
- case 'F': /* text search subsystem */
- switch (cmd[2])
- {
- case '\0':
- case '+':
- success = listTSConfigs(pattern, show_verbose);
- break;
- case 'p':
- success = listTSParsers(pattern, show_verbose);
- break;
- case 'd':
- success = listTSDictionaries(pattern, show_verbose);
- break;
- case 't':
- success = listTSTemplates(pattern, show_verbose);
- break;
- default:
- status = PSQL_CMD_UNKNOWN;
- break;
- }
- break;
- case 'e': /* SQL/MED subsystem */
- switch (cmd[2])
- {
- case 's':
- success = listForeignServers(pattern, show_verbose);
- break;
- case 'u':
- success = listUserMappings(pattern, show_verbose);
- break;
- case 'w':
- success = listForeignDataWrappers(pattern, show_verbose);
- break;
- case 't':
- success = listForeignTables(pattern, show_verbose);
- break;
- default:
- status = PSQL_CMD_UNKNOWN;
- break;
- }
- break;
- case 'x': /* Extensions */
- if (show_verbose)
- success = listExtensionContents(pattern);
- else
- success = listExtensions(pattern);
- break;
- case 'y': /* Event Triggers */
- success = listEventTriggers(pattern, show_verbose);
- break;
- default:
- status = PSQL_CMD_UNKNOWN;
- }
- if (pattern)
- free(pattern);
- }
- /*
- * \e or \edit -- edit the current query buffer, or edit a file and make
- * it the query buffer
- */
- else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
- {
- if (!query_buf)
- {
- psql_error("no query buffer\n");
- status = PSQL_CMD_ERROR;
- }
- else
- {
- char *fname;
- char *ln = NULL;
- int lineno = -1;
- fname = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- if (fname)
- {
- /* try to get separate lineno arg */
- ln = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- if (ln == NULL)
- {
- /* only one arg; maybe it is lineno not fname */
- if (fname[0] &&
- strspn(fname, "0123456789") == strlen(fname))
- {
- /* all digits, so assume it is lineno */
- ln = fname;
- fname = NULL;
- }
- }
- }
- if (ln)
- {
- lineno = atoi(ln);
- if (lineno < 1)
- {
- psql_error("invalid line number: %s\n", ln);
- status = PSQL_CMD_ERROR;
- }
- }
- if (status != PSQL_CMD_ERROR)
- {
- expand_tilde(&fname);
- if (fname)
- canonicalize_path(fname);
- if (do_edit(fname, query_buf, lineno, NULL))
- status = PSQL_CMD_NEWEDIT;
- else
- status = PSQL_CMD_ERROR;
- }
- if (fname)
- free(fname);
- if (ln)
- free(ln);
- }
- }
- /*
- * \ef -- edit the named function, or present a blank CREATE FUNCTION
- * template if no argument is given
- */
- else if (strcmp(cmd, "ef") == 0)
- {
- int lineno = -1;
- if (pset.sversion < 80400)
- {
- psql_error("The server (version %d.%d) does not support editing function source.\n",
- pset.sversion / 10000, (pset.sversion / 100) % 100);
- status = PSQL_CMD_ERROR;
- }
- else if (!query_buf)
- {
- psql_error("no query buffer\n");
- status = PSQL_CMD_ERROR;
- }
- else
- {
- char *func;
- Oid foid = InvalidOid;
- func = psql_scan_slash_option(scan_state,
- OT_WHOLE_LINE, NULL, true);
- lineno = strip_lineno_from_funcdesc(func);
- if (lineno == 0)
- {
- /* error already reported */
- status = PSQL_CMD_ERROR;
- }
- else if (!func)
- {
- /* set up an empty command to fill in */
- printfPQExpBuffer(query_buf,
- "CREATE FUNCTION ( )\n"
- " RETURNS \n"
- " LANGUAGE \n"
- " -- common options: IMMUTABLE STABLE STRICT SECURITY DEFINER\n"
- "AS $function$\n"
- "\n$function$\n");
- }
- else if (!lookup_function_oid(pset.db, func, &foid))
- {
- /* error already reported */
- status = PSQL_CMD_ERROR;
- }
- else if (!get_create_function_cmd(pset.db, foid, query_buf))
- {
- /* error already reported */
- status = PSQL_CMD_ERROR;
- }
- else if (lineno > 0)
- {
- /*
- * lineno "1" should correspond to the first line of the
- * function body. We expect that pg_get_functiondef() will
- * emit that on a line beginning with "AS ", and that there
- * can be no such line before the real start of the function
- * body. Increment lineno by the number of lines before that
- * line, so that it becomes relative to the first line of the
- * function definition.
- */
- const char *lines = query_buf->data;
- while (*lines != '\0')
- {
- if (strncmp(lines, "AS ", 3) == 0)
- break;
- lineno++;
- /* find start of next line */
- lines = strchr(lines, '\n');
- if (!lines)
- break;
- lines++;
- }
- }
- if (func)
- free(func);
- }
- if (status != PSQL_CMD_ERROR)
- {
- bool edited = false;
- if (!do_edit(NULL, query_buf, lineno, &edited))
- status = PSQL_CMD_ERROR;
- else if (!edited)
- puts(_("No changes"));
- else
- status = PSQL_CMD_NEWEDIT;
- }
- }
- /* \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, "ed, 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, pset.quiet);
- free(fname);
- }
- /* \g [filename] -- send query, optionally with output to file/pipe */
- 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 = PSQL_CMD_SEND;
- }
- /* \gset [prefix] -- send query and store result into variables */
- else if (strcmp(cmd, "gset") == 0)
- {
- char *prefix = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, false);
- if (prefix)
- pset.gset_prefix = prefix;
- else
- {
- /* we must set a non-NULL prefix to trigger storing */
- pset.gset_prefix = pg_strdup("");
- }
- /* gset_prefix is freed later */
- status = PSQL_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);
- size_t len;
- /* strip any trailing spaces and semicolons */
- if (opt)
- {
- len = strlen(opt);
- while (len > 0 &&
- (isspace((unsigned char) opt[len - 1])
- || opt[len - 1] == ';'))
- opt[--len] = '\0';
- }
- 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, pset.quiet);
- else
- success = do_pset("format", "aligned", &pset.popt, pset.quiet);
- }
- /* \i and \ir include files */
- else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
- || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 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
- {
- bool include_relative;
- include_relative = (strcmp(cmd, "ir") == 0
- || strcmp(cmd, "include_relative") == 0);
- expand_tilde(&fname);
- success = (process_file(fname, false, include_relative) == EXIT_SUCCESS);
- free(fname);
- }
- }
- /* \l is list databases */
- else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
- strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
- {
- char *pattern;
- bool show_verbose;
- pattern = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- show_verbose = strchr(cmd, '+') ? true : false;
- success = listAllDbs(pattern, show_verbose);
- if (pattern)
- free(pattern);
- }
- /*
- * 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 = PSQL_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 (!pset.quiet)
- puts(_("Query buffer is empty."));
- fflush(stdout);
- }
- /* \password -- set user password */
- else if (strcmp(cmd, "password") == 0)
- {
- char *pw1;
- char *pw2;
- pw1 = simple_prompt("Enter new password: ", 100, false);
- pw2 = simple_prompt("Enter it again: ", 100, false);
- if (strcmp(pw1, pw2) != 0)
- {
- psql_error("Passwords didn't match.\n");
- success = false;
- }
- else
- {
- char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true);
- char *user;
- char *encrypted_password;
- if (opt0)
- user = opt0;
- else
- user = PQuser(pset.db);
- encrypted_password = PQencryptPassword(pw1, user);
- if (!encrypted_password)
- {
- psql_error("Password encryption failed.\n");
- success = false;
- }
- else
- {
- PQExpBufferData buf;
- PGresult *res;
- initPQExpBuffer(&buf);
- printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
- fmtId(user));
- appendStringLiteralConn(&buf, encrypted_password, pset.db);
- res = PSQLexec(buf.data, false);
- termPQExpBuffer(&buf);
- if (!res)
- success = false;
- else
- PQclear(res);
- PQfreemem(encrypted_password);
- }
- if (opt0)
- free(opt0);
- }
- free(pw1);
- free(pw2);
- }
- /* \prompt -- prompt and set variable */
- else if (strcmp(cmd, "prompt") == 0)
- {
- char *opt,
- *prompt_text = NULL;
- char *arg1,
- *arg2;
- arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
- arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
- if (!arg1)
- {
- psql_error("\\%s: missing required argument\n", cmd);
- success = false;
- }
- else
- {
- char *result;
- if (arg2)
- {
- prompt_text = arg1;
- opt = arg2;
- }
- else
- opt = arg1;
- if (!pset.inputfile)
- result = simple_prompt(prompt_text, 4096, true);
- else
- {
- if (prompt_text)
- {
- fputs(prompt_text, stdout);
- fflush(stdout);
- }
- result = gets_fromFile(stdin);
- }
- if (!SetVariable(pset.vars, opt, result))
- {
- psql_error("\\%s: error while setting variable\n", cmd);
- success = false;
- }
- free(result);
- if (prompt_text)
- free(prompt_text);
- free(opt);
- }
- }
- /* \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)
- {
- /* list all variables */
- int i;
- static const char *const my_list[] = {
- "border", "columns", "expanded", "fieldsep",
- "footer", "format", "linestyle", "null",
- "numericlocale", "pager", "recordsep",
- "tableattr", "title", "tuples_only",
- NULL
- };
- for (i = 0; my_list[i] != NULL; i++)
- printPsetInfo(my_list[i], &pset.popt);
- success = true;
- }
- else
- success = do_pset(opt0, opt1, &pset.popt, pset.quiet);
- free(opt0);
- free(opt1);
- }
- /* \q or \quit */
- else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
- status = PSQL_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 (!pset.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 = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- #if defined(WIN32) && !defined(__CYGWIN__)
- /*
- * XXX This does not work for all terminal environments or for output
- * containing non-ASCII characters; see comments in simple_prompt().
- */
- #define DEVTTY "con"
- #else
- #define DEVTTY "/dev/tty"
- #endif
- expand_tilde(&fname);
- /* This scrolls off the screen when using /dev/tty */
- success = saveHistory(fname ? fname : DEVTTY, -1, false, false);
- if (success && !pset.quiet && fname)
- printf(_("Wrote history to file \"%s\".\n"), fname);
- if (!fname)
- putchar('\n');
- 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))
- {
- psql_error("\\%s: error while setting variable\n", cmd);
- success = false;
- }
- free(newval);
- }
- free(opt0);
- }
- /* \setenv -- set environment command */
- else if (strcmp(cmd, "setenv") == 0)
- {
- char *envvar = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, false);
- char *envval = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, false);
- if (!envvar)
- {
- psql_error("\\%s: missing required argument\n", cmd);
- success = false;
- }
- else if (strchr(envvar, '=') != NULL)
- {
- psql_error("\\%s: environment variable name must not contain \"=\"\n",
- cmd);
- success = false;
- }
- else if (!envval)
- {
- /* No argument - unset the environment variable */
- unsetenv(envvar);
- success = true;
- }
- else
- {
- /* Set variable to the value of the next argument */
- char *newval;
- newval = psprintf("%s=%s", envvar, envval);
- putenv(newval);
- success = true;
- /*
- * Do not free newval here, it will screw up the environment if
- * you do. See putenv man page for details. That means we leak a
- * bit of memory here, but not enough to worry about.
- */
- }
- free(envvar);
- free(envval);
- }
- /* \sf -- show a function's source code */
- else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
- {
- bool show_linenumbers = (strcmp(cmd, "sf+") == 0);
- PQExpBuffer func_buf;
- char *func;
- Oid foid = InvalidOid;
- func_buf = createPQExpBuffer();
- func = psql_scan_slash_option(scan_state,
- OT_WHOLE_LINE, NULL, true);
- if (pset.sversion < 80400)
- {
- psql_error("The server (version %d.%d) does not support showing function source.\n",
- pset.sversion / 10000, (pset.sversion / 100) % 100);
- status = PSQL_CMD_ERROR;
- }
- else if (!func)
- {
- psql_error("function name is required\n");
- status = PSQL_CMD_ERROR;
- }
- else if (!lookup_function_oid(pset.db, func, &foid))
- {
- /* error already reported */
- status = PSQL_CMD_ERROR;
- }
- else if (!get_create_function_cmd(pset.db, foid, func_buf))
- {
- /* error already reported */
- status = PSQL_CMD_ERROR;
- }
- else
- {
- FILE *output;
- bool is_pager;
- /* Select output stream: stdout, pager, or file */
- if (pset.queryFout == stdout)
- {
- /* count lines in function to see if pager is needed */
- int lineno = 0;
- const char *lines = func_buf->data;
- while (*lines != '\0')
- {
- lineno++;
- /* find start of next line */
- lines = strchr(lines, '\n');
- if (!lines)
- break;
- lines++;
- }
- output = PageOutput(lineno, pset.popt.topt.pager);
- is_pager = true;
- }
- else
- {
- /* use previously set output file, without pager */
- output = pset.queryFout;
- is_pager = false;
- }
- if (show_linenumbers)
- {
- bool in_header = true;
- int lineno = 0;
- char *lines = func_buf->data;
- /*
- * lineno "1" should correspond to the first line of the
- * function body. We expect that pg_get_functiondef() will
- * emit that on a line beginning with "AS ", and that there
- * can be no such line before the real start of the function
- * body.
- *
- * Note that this loop scribbles on func_buf.
- */
- while (*lines != '\0')
- {
- char *eol;
- if (in_header && strncmp(lines, "AS ", 3) == 0)
- in_header = false;
- /* increment lineno only for body's lines */
- if (!in_header)
- lineno++;
- /* find and mark end of current line */
- eol = strchr(lines, '\n');
- if (eol != NULL)
- *eol = '\0';
- /* show current line as appropriate */
- if (in_header)
- fprintf(output, " %s\n", lines);
- else
- fprintf(output, "%-7d %s\n", lineno, lines);
- /* advance to next line, if any */
- if (eol == NULL)
- break;
- lines = ++eol;
- }
- }
- else
- {
- /* just send the function definition to output */
- fputs(func_buf->data, output);
- }
- if (is_pager)
- ClosePager(output);
- }
- if (func)
- free(func);
- destroyPQExpBuffer(func_buf);
- }
- /* \t -- turn off headers and row count */
- else if (strcmp(cmd, "t") == 0)
- {
- char *opt = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
- free(opt);
- }
- /* \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, pset.quiet);
- free(value);
- }
- /* \timing -- toggle timing of queries */
- else if (strcmp(cmd, "timing") == 0)
- {
- char *opt = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, false);
- if (opt)
- pset.timing = ParseVariableBool(opt);
- else
- pset.timing = !pset.timing;
- if (!pset.quiet)
- {
- if (pset.timing)
- puts(_("Timing is on."));
- else
- puts(_("Timing is off."));
- }
- free(opt);
- }
- /* \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 while setting variable\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 = PSQL_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
- {
- canonicalize_path(fname);
- 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);
- }
- /* \watch -- execute a query every N seconds */
- else if (strcmp(cmd, "watch") == 0)
- {
- char *opt = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- long sleep = 2;
- /* Convert optional sleep-length argument */
- if (opt)
- {
- sleep = strtol(opt, NULL, 10);
- if (sleep <= 0)
- sleep = 1;
- free(opt);
- }
- success = do_watch(query_buf, sleep);
- /* Reset the query buffer as though for \r */
- resetPQExpBuffer(query_buf);
- psql_scan_reset(scan_state);
- }
- /* \x -- set or toggle expanded table representation */
- else if (strcmp(cmd, "x") == 0)
- {
- char *opt = psql_scan_slash_option(scan_state,
- OT_NORMAL, NULL, true);
- success = do_pset("expanded", opt, &pset.popt, pset.quiet);
- free(opt);
- }
- /* \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)))
- {
- psql_error("+ opt(%d) = |%s|\n", i++, value);
- free(value);
- }
- }
- #endif
- else
- status = PSQL_CMD_UNKNOWN;
- if (!success)
- status = PSQL_CMD_ERROR;
- return status;
- }
- /*
- * Ask the user for a password; 'username' is the username the
- * password is for, if one has been explicitly specified. Returns a
- * malloc'd string.
- */
- static char *
- prompt_for_password(const char *username)
- {
- char *result;
- if (username == NULL)
- result = simple_prompt("Password: ", 100, false);
- else
- {
- char *prompt_text;
- prompt_text = psprintf(_("Password for user %s: "), username);
- result = simple_prompt(prompt_text, 100, false);
- free(prompt_text);
- }
- return result;
- }
- static bool
- param_is_newly_set(const char *old_val, const char *new_val)
- {
- if (new_val == NULL)
- return false;
- if (old_val == NULL || strcmp(old_val, new_val) != 0)
- return true;
- return false;
- }
- /*
- * do_connect -- handler for \connect
- *
- * Connects to a database with given parameters. If there exists an
- * established connection, NULL values will be replaced with the ones
- * in the current connection. Otherwise NULL will be passed for that
- * parameter to PQconnectdbParams(), so the libpq defaults will be used.
- *
- * In interactive mode, if connection fails with the given parameters,
- * the old connection will be kept.
- */
- static bool
- do_connect(char *dbname, char *user, char *host, char *port)
- {
- PGconn *o_conn = pset.db,
- *n_conn;
- char *password = NULL;
- if (!o_conn && (!dbname || !user || !host || !port))
- {
- /*
- * We don't know the supplied connection parameters and don't want to
- * connect to the wrong database by using defaults, so require all
- * parameters to be specified.
- */
- psql_error("All connection parameters must be supplied because no "
- "database connection exists\n");
- return false;
- }
- if (!dbname)
- dbname = PQdb(o_conn);
- if (!user)
- user = PQuser(o_conn);
- if (!host)
- host = PQhost(o_conn);
- if (!port)
- port = PQport(o_conn);
- /*
- * If the user asked to be prompted for a password, ask for one now. If
- * not, use the password from the old connection, provided the username
- * has not changed. Otherwise, try to connect without a password first,
- * and then ask for a password if needed.
- *
- * XXX: this behavior leads to spurious connection attempts recorded in
- * the postmaster's log. But libpq offers no API that would let us obtain
- * a password and then continue with the first connection attempt.
- */
- if (pset.getPassword == TRI_YES)
- {
- password = prompt_for_password(user);
- }
- else if (o_conn && user && strcmp(PQuser(o_conn), user) == 0)
- {
- password = pg_strdup(PQpass(o_conn));
- }
- while (true)
- {
- #define PARAMS_ARRAY_SIZE 8
- const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords));
- const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values));
- keywords[0] = "host";
- values[0] = host;
- keywords[1] = "port";
- values[1] = port;
- keywords[2] = "user";
- values[2] = user;
- keywords[3] = "password";
- values[3] = password;
- keywords[4] = "dbname";
- values[4] = dbname;
- keywords[5] = "fallback_application_name";
- values[5] = pset.progname;
- keywords[6] = "client_encoding";
- values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
- keywords[7] = NULL;
- values[7] = NULL;
- n_conn = PQconnectdbParams(keywords, values, true);
- free(keywords);
- free(values);
- /* We can immediately discard the password -- no longer needed */
- if (password)
- free(password);
- if (PQstatus(n_conn) == CONNECTION_OK)
- break;
- /*
- * Connection attempt failed; either retry the connection attempt with
- * a new password, or give up.
- */
- if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
- {
- PQfinish(n_conn);
- password = prompt_for_password(user);
- continue;
- }
- /*
- * Failed to connect to the database. In interactive mode, keep the
- * previous connection to the DB; in scripting mode, close our
- * previous connection as well.
- */
- if (pset.cur_cmd_interactive)
- {
- psql_error("%s", PQerrorMessage(n_conn));
- /* pset.db is left unmodified */
- if (o_conn)
- psql_error("Previous connection kept\n");
- }
- else
- {
- psql_error("\\connect: %s", PQerrorMessage(n_conn));
- if (o_conn)
- {
- PQfinish(o_conn);
- pset.db = NULL;
- }
- }
- PQfinish(n_conn);
- return false;
- }
- /*
- * Replace the old connection with the new one, and update
- * connection-dependent variables.
- */
- PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL);
- pset.db = n_conn;
- SyncVariables();
- connection_warnings(false); /* Must be after SyncVariables */
- /* Tell the user about the new connection */
- if (!pset.quiet)
- {
- if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) ||
- param_is_newly_set(PQport(o_conn), PQport(pset.db)))
- {
- char *host = PQhost(pset.db);
- if (host == NULL)
- host = DEFAULT_PGSOCKET_DIR;
- /* If the host is an absolute path, the connection is via socket */
- if (is_absolute_path(host))
- printf(_("You are now connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
- PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
- else
- printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
- PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
- }
- else
- printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
- PQdb(pset.db), PQuser(pset.db));
- }
- if (o_conn)
- PQfinish(o_conn);
- return true;
- }
- void
- connection_warnings(bool in_startup)
- {
- if (!pset.quiet && !pset.notty)
- {
- int client_ver = PG_VERSION_NUM;
- if (pset.sversion != client_ver)
- {
- const char *server_version;
- char server_ver_str[16];
- /* Try to get full text form, might include "devel" etc */
- server_version = PQparameterStatus(pset.db, "server_version");
- if (!server_version)
- {
- snprintf(server_ver_str, sizeof(server_ver_str),
- "%d.%d.%d",
- pset.sversion / 10000,
- (pset.sversion / 100) % 100,
- pset.sversion % 100);
- server_version = server_ver_str;
- }
- printf(_("%s (%s, server %s)\n"),
- pset.progname, PG_VERSION, server_version);
- }
- /* For version match, only print psql banner on startup. */
- else if (in_startup)
- printf("%s (%s)\n", pset.progname, PG_VERSION);
- if (pset.sversion / 100 > client_ver / 100)
- printf(_("WARNING: %s major version %d.%d, server major version %d.%d.\n"
- " Some psql features might not work.\n"),
- pset.progname, client_ver / 10000, (client_ver / 100) % 100,
- pset.sversion / 10000, (pset.sversion / 100) % 100);
- #ifdef WIN32
- checkWin32Codepage();
- #endif
- printSSLInfo();
- }
- }
- /*
- * printSSLInfo
- *
- * Prints information about the current SSL connection, if SSL is in use
- */
- static void
- printSSLInfo(void)
- {
- #ifdef USE_OPENSSL
- int sslbits = -1;
- SSL *ssl;
- ssl = PQgetssl(pset.db);
- if (!ssl)
- return; /* no SSL */
- SSL_get_cipher_bits(ssl, &sslbits);
- printf(_("SSL connection (protocol: %s, cipher: %s, bits: %d, compression: %s)\n"),
- SSL_get_version(ssl), SSL_get_cipher(ssl), sslbits,
- SSL_get_current_compression(ssl) ? _("on") : _("off"));
- #else
- /*
- * If psql is compiled without SSL but is using a libpq with SSL, we
- * cannot figure out the specifics about the connection. But we know it's
- * SSL secured.
- */
- if (PQgetssl(pset.db))
- printf(_("SSL connection (unknown cipher)\n"));
- #endif
- }
- /*
- * checkWin32Codepage
- *
- * Prints a warning when win32 console codepage differs from Windows codepage
- */
- #ifdef WIN32
- static void
- checkWin32Codepage(void)
- {
- unsigned int wincp,
- concp;
- wincp = GetACP();
- concp = GetConsoleCP();
- if (wincp != concp)
- {
- printf(_("WARNING: Console code page (%u) differs from Windows code page (%u)\n"
- " 8-bit characters might not work correctly. See psql reference\n"
- " page \"Notes for Windows users\" for details.\n"),
- concp, wincp);
- }
- }
- #endif
- /*
- * 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;
- pset.sversion = PQserverVersion(pset.db);
- 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 */
- PQsetErrorVerbosity(pset.db, pset.verbosity);
- }
- /*
- * 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);
- }
- /*
- * 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, int lineno)
- {
- const char *editorName;
- const char *editor_lineno_arg = NULL;
- char *sys;
- int result;
- Assert(fname != NULL);
- /* Find an editor to use */
- editorName = getenv("PSQL_EDITOR");
- if (!editorName)
- editorName = getenv("EDITOR");
- if (!editorName)
- editorName = getenv("VISUAL");
- if (!editorName)
- editorName = DEFAULT_EDITOR;
- /* Get line number argument, if we need it. */
- if (lineno > 0)
- {
- editor_lineno_arg = getenv("PSQL_EDITOR_LINENUMBER_ARG");
- #ifdef DEFAULT_EDITOR_LINENUMBER_ARG
- if (!editor_lineno_arg)
- editor_lineno_arg = DEFAULT_EDITOR_LINENUMBER_ARG;
- #endif
- if (!editor_lineno_arg)
- {
- psql_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number\n");
- return false;
- }
- }
- /*
- * On Unix the EDITOR value should *not* be quoted, since it might include
- * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
- * if necessary. But this policy is not very workable on Windows, due to
- * severe brain damage in their command shell plus the fact that standard
- * program paths include spaces.
- */
- #ifndef WIN32
- if (lineno > 0)
- sys = psprintf("exec %s %s%d '%s'",
- editorName, editor_lineno_arg, lineno, fname);
- else
- sys = psprintf("exec %s '%s'",
- editorName, fname);
- #else
- if (lineno > 0)
- sys = psprintf("\"%s\" %s%d \"%s\"",
- editorName, editor_lineno_arg, lineno, fname);
- else
- sys = psprintf("\"%s\" \"%s\"",
- editorName, fname);
- #endif
- 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,
- int lineno, bool *edited)
- {
- char fnametmp[MAXPGPATH];
- FILE *stream = NULL;
- const char *fname;
- bool error = false;
- int fd;
- struct stat before,
- after;
- if (filename_arg)
- fname = filename_arg;
- else
- {
- /* make a temp file to edit */
- #ifndef WIN32
- const char *tmpdir = getenv("TMPDIR");
- if (!tmpdir)
- tmpdir = "/tmp";
- #else
- char tmpdir[MAXPGPATH];
- int ret;
- ret = GetTempPath(MAXPGPATH, tmpdir);
- if (ret == 0 || ret > MAXPGPATH)
- {
- psql_error("could not locate temporary directory: %s\n",
- !ret ? strerror(errno) : "");
- return false;
- }
- /*
- * No canonicalize_path() here. EDIT.EXE run from CMD.EXE prepends the
- * current directory to the supplied path unless we use only
- * backslashes, so we do that.
- */
- #endif
- #ifndef WIN32
- snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
- "/", (int) getpid());
- #else
- snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
- "" /* trailing separator already present */ , (int) getpid());
- #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));
- if (fclose(stream) != 0)
- psql_error("%s: %s\n", fname, strerror(errno));
- if (remove(fname) != 0)
- psql_error("%s: %s\n", fname, strerror(errno));
- error = true;
- }
- else if (fclose(stream) != 0)
- {
- psql_error("%s: %s\n", fname, strerror(errno));
- if (remove(fname) != 0)
- psql_error("%s: %s\n", fname, strerror(errno));
- error = true;
- }
- }
- }
- if (!error && stat(fname, &before) != 0)
- {
- psql_error("%s: %s\n", fname, strerror(errno));
- error = true;
- }
- /* call editor */
- if (!error)
- error = !editFile(fname, lineno);
- if (!error && stat(fname, &after) != 0)
- {
- psql_error("%s: %s\n", fname, strerror(errno));
- error = true;
- }
- if (!error && before.st_mtime != after.st_mtime)
- {
- stream = fopen(fname, PG_BINARY_R);
- if (!stream)
- {
- psql_error("%s: %s\n", fname, strerror(errno));
- error = true;
- }
- else
- {
- /* read file back into query_buf */
- 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;
- }
- else if (edited)
- {
- *edited = 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
- *
- * Reads commands from filename and passes them to the main processing loop.
- * Handler for \i and \ir, but can be used for other things as well. Returns
- * MainLoop() error code.
- *
- * If use_relative_path is true and filename is not an absolute path, then open
- * the file from where the currently processed file (if any) is located.
- */
- int
- process_file(char *filename, bool single_txn, bool use_relative_path)
- {
- FILE *fd;
- int result;
- char *oldfilename;
- char relpath[MAXPGPATH];
- PGresult *res;
- if (!filename)
- {
- fd = stdin;
- filename = NULL;
- }
- else if (strcmp(filename, "-") != 0)
- {
- canonicalize_path(filename);
- /*
- * If we were asked to resolve the pathname relative to the location
- * of the currently executing script, and there is one, and this is a
- * relative pathname, then prepend all but the last pathname compon…
Large files files are truncated, but you can click here to view the full file