/* * psql - the PostgreSQL interactive terminal * * Copyright (c) 2000-2023, PostgreSQL Global Development Group * * src/bin/psql/command.c */ #include "postgres_fe.h" #include #include #include #include #ifndef WIN32 #include /* for stat() */ #include /* for setitimer() */ #include /* open() flags */ #include /* for geteuid(), getpid(), stat() */ #else #include #include #include #include #include /* for stat() */ #endif #include "catalog/pg_class_d.h" #include "command.h" #include "common.h" #include "common/logging.h" #include "common/string.h" #include "copy.h" #include "crosstabview.h" #include "describe.h" #include "fe_utils/cancel.h" #include "fe_utils/print.h" #include "fe_utils/string_utils.h" #include "help.h" #include "input.h" #include "large_obj.h" #include "libpq-fe.h" #include "libpq/pqcomm.h" #include "mainloop.h" #include "portability/instr_time.h" #include "pqexpbuffer.h" #include "psqlscanslash.h" #include "settings.h" #include "variables.h" /* * Editable database object types. */ typedef enum EditableObjectType { EditableFunction, EditableView } EditableObjectType; /* local function declarations */ static backslashResult exec_command(const char *cmd, PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf, PQExpBuffer previous_buf); static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_bind(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd); static bool exec_command_dfo(PsqlScanState scan_state, const char *cmd, const char *pattern, bool show_verbose, bool show_system); static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, PQExpBuffer previous_buf); static backslashResult exec_command_ef_ev(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, bool is_func); static backslashResult exec_command_echo(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf); static backslashResult exec_command_else(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf); static backslashResult exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf); static backslashResult exec_command_encoding(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult process_command_g_options(char *first_option, PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_gdesc(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_getenv(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_html(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_include(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_if(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf); static backslashResult exec_command_list(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, PQExpBuffer previous_buf); static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf); static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_branch, const char *cmd, bool is_func); static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch, const char *cmd, PQExpBuffer query_buf, PQExpBuffer previous_buf); static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, PQExpBuffer previous_buf); static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch, const char *cmd); static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch); static char *read_connect_arg(PsqlScanState scan_state); static PQExpBuffer gather_boolean_expression(PsqlScanState scan_state); static bool is_true_boolean_expression(PsqlScanState scan_state, const char *name); static void ignore_boolean_expression(PsqlScanState scan_state); static void ignore_slash_options(PsqlScanState scan_state); static void ignore_slash_filepipe(PsqlScanState scan_state); static void ignore_slash_whole_line(PsqlScanState scan_state); static bool is_branching_command(const char *cmd); static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf); static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf); static bool copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf); static bool do_connect(enum trivalue reuse_previous_specification, char *dbname, char *user, char *host, char *port); static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool discard_on_quit, bool *edited); static bool do_shell(const char *command); static bool do_watch(PQExpBuffer query_buf, double sleep, int iter); static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, Oid *obj_oid); static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid, PQExpBuffer buf); static int strip_lineno_from_objdesc(char *obj); static int count_lines_in_buf(PQExpBuffer buf); static void print_with_linenumbers(FILE *output, char *lines, bool is_func); static void minimal_error_message(PGresult *res); static void printSSLInfo(void); static void printGSSInfo(void); static bool printPsetInfo(const char *param, printQueryOpt *popt); static char *pset_value_string(const char *param, 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. * * cstack is the current \if stack state. This will be examined, and * possibly modified by conditional commands. * * query_buf contains the query-so-far, which may be modified by * execution of the backslash command (for example, \r clears it). * * previous_buf contains the query most recently sent to the server * (empty if none yet). This should not be modified here, but some * commands copy its content into query_buf. * * query_buf and previous_buf will be NULL when executing a "-c" * command-line option. * * Returns a status code indicating what action is desired, see command.h. *---------- */ backslashResult HandleSlashCmds(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf, PQExpBuffer previous_buf) { backslashResult status; char *cmd; char *arg; Assert(scan_state != NULL); Assert(cstack != NULL); /* Parse off the command name */ cmd = psql_scan_slash_command(scan_state); /* And try to execute it */ status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf); if (status == PSQL_CMD_UNKNOWN) { pg_log_error("invalid command \\%s", cmd); if (pset.cur_cmd_interactive) pg_log_error_hint("Try \\? for help."); status = PSQL_CMD_ERROR; } if (status != PSQL_CMD_ERROR) { /* * Eat any remaining arguments after a valid command. We want to * suppress evaluation of backticks in this situation, so transiently * push an inactive conditional-stack entry. */ bool active_branch = conditional_active(cstack); conditional_stack_push(cstack, IFSTATE_IGNORED); while ((arg = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false))) { if (active_branch) pg_log_warning("\\%s: extra argument \"%s\" ignored", cmd, arg); free(arg); } conditional_stack_pop(cstack); } 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; } /* * Subroutine to actually try to execute a backslash command. * * The typical "success" result code is PSQL_CMD_SKIP_LINE, although some * commands return something else. Failure result code is PSQL_CMD_ERROR, * unless PSQL_CMD_UNKNOWN is more appropriate. */ static backslashResult exec_command(const char *cmd, PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf, PQExpBuffer previous_buf) { backslashResult status; bool active_branch = conditional_active(cstack); /* * In interactive mode, warn when we're ignoring a command within a false * \if-branch. But we continue on, so as to parse and discard the right * amount of parameter text. Each individual backslash command subroutine * is responsible for doing nothing after discarding appropriate * arguments, if !active_branch. */ if (pset.cur_cmd_interactive && !active_branch && !is_branching_command(cmd)) { pg_log_warning("\\%s command ignored; use \\endif or Ctrl-C to exit current \\if block", cmd); } if (strcmp(cmd, "a") == 0) status = exec_command_a(scan_state, active_branch); else if (strcmp(cmd, "bind") == 0) status = exec_command_bind(scan_state, active_branch); else if (strcmp(cmd, "C") == 0) status = exec_command_C(scan_state, active_branch); else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) status = exec_command_connect(scan_state, active_branch); else if (strcmp(cmd, "cd") == 0) status = exec_command_cd(scan_state, active_branch, cmd); else if (strcmp(cmd, "conninfo") == 0) status = exec_command_conninfo(scan_state, active_branch); else if (pg_strcasecmp(cmd, "copy") == 0) status = exec_command_copy(scan_state, active_branch); else if (strcmp(cmd, "copyright") == 0) status = exec_command_copyright(scan_state, active_branch); else if (strcmp(cmd, "crosstabview") == 0) status = exec_command_crosstabview(scan_state, active_branch); else if (cmd[0] == 'd') status = exec_command_d(scan_state, active_branch, cmd); else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) status = exec_command_edit(scan_state, active_branch, query_buf, previous_buf); else if (strcmp(cmd, "ef") == 0) status = exec_command_ef_ev(scan_state, active_branch, query_buf, true); else if (strcmp(cmd, "ev") == 0) status = exec_command_ef_ev(scan_state, active_branch, query_buf, false); else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0 || strcmp(cmd, "warn") == 0) status = exec_command_echo(scan_state, active_branch, cmd); else if (strcmp(cmd, "elif") == 0) status = exec_command_elif(scan_state, cstack, query_buf); else if (strcmp(cmd, "else") == 0) status = exec_command_else(scan_state, cstack, query_buf); else if (strcmp(cmd, "endif") == 0) status = exec_command_endif(scan_state, cstack, query_buf); else if (strcmp(cmd, "encoding") == 0) status = exec_command_encoding(scan_state, active_branch); else if (strcmp(cmd, "errverbose") == 0) status = exec_command_errverbose(scan_state, active_branch); else if (strcmp(cmd, "f") == 0) status = exec_command_f(scan_state, active_branch); else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0) status = exec_command_g(scan_state, active_branch, cmd); else if (strcmp(cmd, "gdesc") == 0) status = exec_command_gdesc(scan_state, active_branch); else if (strcmp(cmd, "getenv") == 0) status = exec_command_getenv(scan_state, active_branch, cmd); else if (strcmp(cmd, "gexec") == 0) status = exec_command_gexec(scan_state, active_branch); else if (strcmp(cmd, "gset") == 0) status = exec_command_gset(scan_state, active_branch); else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) status = exec_command_help(scan_state, active_branch); else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) status = exec_command_html(scan_state, active_branch); else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0) status = exec_command_include(scan_state, active_branch, cmd); else if (strcmp(cmd, "if") == 0) status = exec_command_if(scan_state, cstack, query_buf); else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 || strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) status = exec_command_list(scan_state, active_branch, cmd); else if (strncmp(cmd, "lo_", 3) == 0) status = exec_command_lo(scan_state, active_branch, cmd); else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) status = exec_command_out(scan_state, active_branch); else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) status = exec_command_print(scan_state, active_branch, query_buf, previous_buf); else if (strcmp(cmd, "password") == 0) status = exec_command_password(scan_state, active_branch); else if (strcmp(cmd, "prompt") == 0) status = exec_command_prompt(scan_state, active_branch, cmd); else if (strcmp(cmd, "pset") == 0) status = exec_command_pset(scan_state, active_branch); else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) status = exec_command_quit(scan_state, active_branch); else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) status = exec_command_reset(scan_state, active_branch, query_buf); else if (strcmp(cmd, "s") == 0) status = exec_command_s(scan_state, active_branch); else if (strcmp(cmd, "set") == 0) status = exec_command_set(scan_state, active_branch); else if (strcmp(cmd, "setenv") == 0) status = exec_command_setenv(scan_state, active_branch, cmd); else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0) status = exec_command_sf_sv(scan_state, active_branch, cmd, true); else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0) status = exec_command_sf_sv(scan_state, active_branch, cmd, false); else if (strcmp(cmd, "t") == 0) status = exec_command_t(scan_state, active_branch); else if (strcmp(cmd, "T") == 0) status = exec_command_T(scan_state, active_branch); else if (strcmp(cmd, "timing") == 0) status = exec_command_timing(scan_state, active_branch); else if (strcmp(cmd, "unset") == 0) status = exec_command_unset(scan_state, active_branch, cmd); else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) status = exec_command_write(scan_state, active_branch, cmd, query_buf, previous_buf); else if (strcmp(cmd, "watch") == 0) status = exec_command_watch(scan_state, active_branch, query_buf, previous_buf); else if (strcmp(cmd, "x") == 0) status = exec_command_x(scan_state, active_branch); else if (strcmp(cmd, "z") == 0 || strcmp(cmd, "zS") == 0) status = exec_command_z(scan_state, active_branch, cmd); else if (strcmp(cmd, "!") == 0) status = exec_command_shell_escape(scan_state, active_branch); else if (strcmp(cmd, "?") == 0) status = exec_command_slash_command_help(scan_state, active_branch); else status = PSQL_CMD_UNKNOWN; /* * All the commands that return PSQL_CMD_SEND want to execute previous_buf * if query_buf is empty. For convenience we implement that here, not in * the individual command subroutines. */ if (status == PSQL_CMD_SEND) (void) copy_previous_query(query_buf, previous_buf); return status; } /* * \a -- toggle field alignment * * This makes little sense but we keep it around. */ static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { 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); } return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \bind -- set query parameters */ static backslashResult exec_command_bind(PsqlScanState scan_state, bool active_branch) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { char *opt; int nparams = 0; int nalloc = 0; pset.bind_params = NULL; while ((opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false))) { nparams++; if (nparams > nalloc) { nalloc = nalloc ? nalloc * 2 : 1; pset.bind_params = pg_realloc_array(pset.bind_params, char *, nalloc); } pset.bind_params[nparams - 1] = opt; } pset.bind_nparams = nparams; pset.bind_flag = true; } return status; } /* * \C -- override table title (formerly change HTML caption) */ static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("title", opt, &pset.popt, pset.quiet); free(opt); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \c or \connect -- connect to database using the specified parameters. * * \c [-reuse-previous=BOOL] dbname user host port * * Specifying a parameter as '-' is equivalent to omitting it. Examples: * * \c - - hst Connect to current database on current port of * host "hst" as current user. * \c - usr - prt Connect to current database on port "prt" of current host * as user "usr". * \c dbs Connect to database "dbs" on current port of current host * as current user. */ static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { static const char prefix[] = "-reuse-previous="; char *opt1, *opt2, *opt3, *opt4; enum trivalue reuse_previous = TRI_DEFAULT; opt1 = read_connect_arg(scan_state); if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0) { bool on_off; success = ParseVariableBool(opt1 + sizeof(prefix) - 1, "-reuse-previous", &on_off); if (success) { reuse_previous = on_off ? TRI_YES : TRI_NO; free(opt1); opt1 = read_connect_arg(scan_state); } } if (success) /* give up if reuse_previous was invalid */ { opt2 = read_connect_arg(scan_state); opt3 = read_connect_arg(scan_state); opt4 = read_connect_arg(scan_state); success = do_connect(reuse_previous, opt1, opt2, opt3, opt4); free(opt2); free(opt3); free(opt4); } free(opt1); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \cd -- change directory */ static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); char *dir; if (opt) dir = opt; else { #ifndef WIN32 /* This should match get_home_path() */ dir = getenv("HOME"); if (dir == NULL || dir[0] == '\0') { uid_t user_id = geteuid(); struct passwd *pw; errno = 0; /* clear errno before call */ pw = getpwuid(user_id); if (pw) dir = pw->pw_dir; else { pg_log_error("could not get home directory for user ID %ld: %s", (long) user_id, errno ? strerror(errno) : _("user does not exist")); success = false; } } #else /* WIN32 */ /* * On Windows, 'cd' without arguments prints the current * directory, so if someone wants to code this here instead... */ dir = "/"; #endif /* WIN32 */ } if (success && chdir(dir) < 0) { pg_log_error("\\%s: could not change directory to \"%s\": %m", cmd, dir); success = false; } free(opt); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \conninfo -- display information about the current connection */ static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch) { if (active_branch) { char *db = PQdb(pset.db); if (db == NULL) printf(_("You are currently not connected to a database.\n")); else { char *host = PQhost(pset.db); char *hostaddr = PQhostaddr(pset.db); if (is_unixsock_path(host)) { /* hostaddr overrides host */ if (hostaddr && *hostaddr) printf(_("You are connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"), db, PQuser(pset.db), hostaddr, PQport(pset.db)); else 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 { if (hostaddr && *hostaddr && strcmp(host, hostaddr) != 0) printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"), db, PQuser(pset.db), host, hostaddr, 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(); printGSSInfo(); } } return PSQL_CMD_SKIP_LINE; } /* * \copy -- run a COPY command */ static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); success = do_copy(opt); free(opt); } else ignore_slash_whole_line(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \copyright -- print copyright notice */ static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch) { if (active_branch) print_copyright(); return PSQL_CMD_SKIP_LINE; } /* * \crosstabview -- execute a query and display result in crosstab */ static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { int i; for (i = 0; i < lengthof(pset.ctv_args); i++) pset.ctv_args[i] = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); pset.crosstab_flag = true; status = PSQL_CMD_SEND; } else ignore_slash_options(scan_state); return status; } /* * \d* commands */ static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd) { backslashResult status = PSQL_CMD_SKIP_LINE; bool success = true; if (active_branch) { 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': { char *pattern2 = NULL; if (pattern && cmd[2] != '\0' && cmd[2] != '+') pattern2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); switch (cmd[2]) { case '\0': case '+': success = describeAccessMethods(pattern, show_verbose); break; case 'c': success = listOperatorClasses(pattern, pattern2, show_verbose); break; case 'f': success = listOperatorFamilies(pattern, pattern2, show_verbose); break; case 'o': success = listOpFamilyOperators(pattern, pattern2, show_verbose); break; case 'p': success = listOpFamilyFunctions(pattern, pattern2, show_verbose); break; default: status = PSQL_CMD_UNKNOWN; break; } free(pattern2); } break; case 'a': success = describeAggregates(pattern, show_verbose, show_system); break; case 'b': success = describeTablespaces(pattern, show_verbose); break; case 'c': if (strncmp(cmd, "dconfig", 7) == 0) success = describeConfigurationParameters(pattern, show_verbose, show_system); else 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 'p': case 't': case 'w': success = exec_command_dfo(scan_state, cmd, 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, show_system); break; case 'l': success = listLargeObjects(show_verbose); break; case 'L': success = listLanguages(pattern, show_verbose, show_system); break; case 'n': success = listSchemas(pattern, show_verbose, show_system); break; case 'o': success = exec_command_dfo(scan_state, cmd, pattern, show_verbose, show_system); break; case 'O': success = listCollations(pattern, show_verbose, show_system); break; case 'p': success = permissionsList(pattern, show_system); break; case 'P': { switch (cmd[2]) { case '\0': case '+': case 't': case 'i': case 'n': success = listPartitionedTables(&cmd[2], pattern, show_verbose); break; default: status = PSQL_CMD_UNKNOWN; break; } } 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); free(pattern2); } else status = PSQL_CMD_UNKNOWN; break; case 'R': switch (cmd[2]) { case 'p': if (show_verbose) success = describePublications(pattern); else success = listPublications(pattern); break; case 's': success = describeSubscriptions(pattern, show_verbose); break; default: status = PSQL_CMD_UNKNOWN; } break; case 'u': success = describeRoles(pattern, show_verbose, show_system); 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 'X': /* Extended Statistics */ success = listExtendedStats(pattern); break; case 'y': /* Event Triggers */ success = listEventTriggers(pattern, show_verbose); break; default: status = PSQL_CMD_UNKNOWN; } free(pattern); } else ignore_slash_options(scan_state); if (!success) status = PSQL_CMD_ERROR; return status; } /* \df and \do; messy enough to split out of exec_command_d */ static bool exec_command_dfo(PsqlScanState scan_state, const char *cmd, const char *pattern, bool show_verbose, bool show_system) { bool success; char *arg_patterns[FUNC_MAX_ARGS]; int num_arg_patterns = 0; /* Collect argument-type patterns too */ if (pattern) /* otherwise it was just \df or \do */ { char *ap; while ((ap = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true)) != NULL) { arg_patterns[num_arg_patterns++] = ap; if (num_arg_patterns >= FUNC_MAX_ARGS) break; /* protect limited-size array */ } } if (cmd[1] == 'f') success = describeFunctions(&cmd[2], pattern, arg_patterns, num_arg_patterns, show_verbose, show_system); else success = describeOperators(pattern, arg_patterns, num_arg_patterns, show_verbose, show_system); while (--num_arg_patterns >= 0) free(arg_patterns[num_arg_patterns]); return success; } /* * \e or \edit -- edit the current query buffer, or edit a file and * make it the query buffer */ static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, PQExpBuffer previous_buf) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { if (!query_buf) { pg_log_error("no query buffer"); 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) { pg_log_error("invalid line number: %s", ln); status = PSQL_CMD_ERROR; } } if (status != PSQL_CMD_ERROR) { bool discard_on_quit; expand_tilde(&fname); if (fname) { canonicalize_path(fname); /* Always clear buffer if the file isn't modified */ discard_on_quit = true; } else { /* * If query_buf is empty, recall previous query for * editing. But in that case, the query buffer should be * emptied if editing doesn't modify the file. */ discard_on_quit = copy_previous_query(query_buf, previous_buf); } if (do_edit(fname, query_buf, lineno, discard_on_quit, NULL)) status = PSQL_CMD_NEWEDIT; else status = PSQL_CMD_ERROR; } free(fname); free(ln); } } else ignore_slash_options(scan_state); return status; } /* * \ef/\ev -- edit the named function/view, or * present a blank CREATE FUNCTION/VIEW template if no argument is given */ static backslashResult exec_command_ef_ev(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, bool is_func) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { char *obj_desc = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true); int lineno = -1; if (!query_buf) { pg_log_error("no query buffer"); status = PSQL_CMD_ERROR; } else { Oid obj_oid = InvalidOid; EditableObjectType eot = is_func ? EditableFunction : EditableView; lineno = strip_lineno_from_objdesc(obj_desc); if (lineno == 0) { /* error already reported */ status = PSQL_CMD_ERROR; } else if (!obj_desc) { /* set up an empty command to fill in */ resetPQExpBuffer(query_buf); if (is_func) appendPQExpBufferStr(query_buf, "CREATE FUNCTION ( )\n" " RETURNS \n" " LANGUAGE \n" " -- common options: IMMUTABLE STABLE STRICT SECURITY DEFINER\n" "AS $function$\n" "\n$function$\n"); else appendPQExpBufferStr(query_buf, "CREATE VIEW AS\n" " SELECT \n" " -- something...\n"); } else if (!lookup_object_oid(eot, obj_desc, &obj_oid)) { /* error already reported */ status = PSQL_CMD_ERROR; } else if (!get_create_object_cmd(eot, obj_oid, query_buf)) { /* error already reported */ status = PSQL_CMD_ERROR; } else if (is_func && 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 ", "BEGIN ", or * "RETURN ", 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 || strncmp(lines, "BEGIN ", 6) == 0 || strncmp(lines, "RETURN ", 7) == 0) break; lineno++; /* find start of next line */ lines = strchr(lines, '\n'); if (!lines) break; lines++; } } } if (status != PSQL_CMD_ERROR) { bool edited = false; if (!do_edit(NULL, query_buf, lineno, true, &edited)) status = PSQL_CMD_ERROR; else if (!edited) puts(_("No changes")); else status = PSQL_CMD_NEWEDIT; } free(obj_desc); } else ignore_slash_whole_line(scan_state); return status; } /* * \echo, \qecho, and \warn -- echo arguments to stdout, query output, or stderr */ static backslashResult exec_command_echo(PsqlScanState scan_state, bool active_branch, const char *cmd) { if (active_branch) { char *value; char quoted; bool no_newline = false; bool first = true; FILE *fout; if (strcmp(cmd, "qecho") == 0) fout = pset.queryFout; else if (strcmp(cmd, "warn") == 0) fout = stderr; else fout = stdout; while ((value = psql_scan_slash_option(scan_state, OT_NORMAL, "ed, false))) { if (first && !no_newline && !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); } else ignore_slash_options(scan_state); return PSQL_CMD_SKIP_LINE; } /* * \encoding -- set/show client side encoding */ static backslashResult exec_command_encoding(PsqlScanState scan_state, bool active_branch) { if (active_branch) { 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) pg_log_error("%s: invalid encoding name or conversion procedure not found", 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); } } else ignore_slash_options(scan_state); return PSQL_CMD_SKIP_LINE; } /* * \errverbose -- display verbose message from last failed query */ static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool active_branch) { if (active_branch) { if (pset.last_error_result) { char *msg; msg = PQresultVerboseErrorMessage(pset.last_error_result, PQERRORS_VERBOSE, PQSHOW_CONTEXT_ALWAYS); if (msg) { pg_log_error("%s", msg); PQfreemem(msg); } else puts(_("out of memory")); } else puts(_("There is no previous error.")); } return PSQL_CMD_SKIP_LINE; } /* * \f -- change field separator */ static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \g [(pset-option[=pset-value] ...)] [filename/shell-command] * \gx [(pset-option[=pset-value] ...)] [filename/shell-command] * * Send the current query. If pset options are specified, they are made * active just for this query. If a filename or pipe command is given, * the query output goes there. \gx implicitly sets "expanded=on" along * with any other pset options that are specified. */ static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd) { backslashResult status = PSQL_CMD_SKIP_LINE; char *fname; /* * Because the option processing for this is fairly complicated, we do it * and then decide whether the branch is active. */ fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); if (fname && fname[0] == '(') { /* Consume pset options through trailing ')' ... */ status = process_command_g_options(fname + 1, scan_state, active_branch, cmd); free(fname); /* ... and again attempt to scan the filename. */ fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); } if (status == PSQL_CMD_SKIP_LINE && active_branch) { if (!fname) pset.gfname = NULL; else { expand_tilde(&fname); pset.gfname = pg_strdup(fname); } if (strcmp(cmd, "gx") == 0) { /* save settings if not done already, then force expanded=on */ if (pset.gsavepopt == NULL) pset.gsavepopt = savePsetInfo(&pset.popt); pset.popt.topt.expanded = 1; } status = PSQL_CMD_SEND; } free(fname); return status; } /* * Process parenthesized pset options for \g * * Note: okay to modify first_option, but not to free it; caller does that */ static backslashResult process_command_g_options(char *first_option, PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; bool found_r_paren = false; do { char *option; size_t optlen; /* If not first time through, collect a new option */ if (first_option) option = first_option; else { option = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!option) { if (active_branch) { pg_log_error("\\%s: missing right parenthesis", cmd); success = false; } break; } } /* Check for terminating right paren, and remove it from string */ optlen = strlen(option); if (optlen > 0 && option[optlen - 1] == ')') { option[--optlen] = '\0'; found_r_paren = true; } /* If there was anything besides parentheses, parse/execute it */ if (optlen > 0) { /* We can have either "name" or "name=value" */ char *valptr = strchr(option, '='); if (valptr) *valptr++ = '\0'; if (active_branch) { /* save settings if not done already, then apply option */ if (pset.gsavepopt == NULL) pset.gsavepopt = savePsetInfo(&pset.popt); success &= do_pset(option, valptr, &pset.popt, true); } } /* Clean up after this option. We should not free first_option. */ if (first_option) first_option = NULL; else free(option); } while (!found_r_paren); /* If we failed after already changing some options, undo side-effects */ if (!success && active_branch && pset.gsavepopt) { restorePsetInfo(&pset.popt, pset.gsavepopt); pset.gsavepopt = NULL; } return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \gdesc -- describe query result */ static backslashResult exec_command_gdesc(PsqlScanState scan_state, bool active_branch) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { pset.gdesc_flag = true; status = PSQL_CMD_SEND; } return status; } /* * \getenv -- set variable from environment variable */ static backslashResult exec_command_getenv(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { char *myvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); char *envvar = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!myvar || !envvar) { pg_log_error("\\%s: missing required argument", cmd); success = false; } else { char *envval = getenv(envvar); if (envval && !SetVariable(pset.vars, myvar, envval)) success = false; } free(myvar); free(envvar); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \gexec -- send query and execute each field of result */ static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { pset.gexec_flag = true; status = PSQL_CMD_SEND; } return status; } /* * \gset [prefix] -- send query and store result into variables */ static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { 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; } else ignore_slash_options(scan_state); return status; } /* * \help [topic] -- print help about SQL commands */ static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch) { if (active_branch) { 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); } else ignore_slash_whole_line(scan_state); return PSQL_CMD_SKIP_LINE; } /* * \H and \html -- toggle HTML formatting */ static backslashResult exec_command_html(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { 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); } return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \i and \ir -- include a file */ static backslashResult exec_command_include(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); if (!fname) { pg_log_error("\\%s: missing required argument", 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, include_relative) == EXIT_SUCCESS); free(fname); } } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \if -- beginning of an \if..\endif block * * is parsed as a boolean expression. Invalid expressions will emit a * warning and be treated as false. Statements that follow a false expression * will be parsed but ignored. Note that in the case where an \if statement * is itself within an inactive section of a block, then the entire inner * \if..\endif block will be parsed but ignored. */ static backslashResult exec_command_if(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf) { if (conditional_active(cstack)) { /* * First, push a new active stack entry; this ensures that the lexer * will perform variable substitution and backtick evaluation while * scanning the expression. (That should happen anyway, since we know * we're in an active outer branch, but let's be sure.) */ conditional_stack_push(cstack, IFSTATE_TRUE); /* Remember current query state in case we need to restore later */ save_query_text_state(scan_state, cstack, query_buf); /* * Evaluate the expression; if it's false, change to inactive state. */ if (!is_true_boolean_expression(scan_state, "\\if expression")) conditional_stack_poke(cstack, IFSTATE_FALSE); } else { /* * We're within an inactive outer branch, so this entire \if block * will be ignored. We don't want to evaluate the expression, so push * the "ignored" stack state before scanning it. */ conditional_stack_push(cstack, IFSTATE_IGNORED); /* Remember current query state in case we need to restore later */ save_query_text_state(scan_state, cstack, query_buf); ignore_boolean_expression(scan_state); } return PSQL_CMD_SKIP_LINE; } /* * \elif -- alternative branch in an \if..\endif block * * is evaluated the same as in \if . */ static backslashResult exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf) { bool success = true; switch (conditional_stack_peek(cstack)) { case IFSTATE_TRUE: /* * Just finished active branch of this \if block. Update saved * state so we will keep whatever data was put in query_buf by the * active branch. */ save_query_text_state(scan_state, cstack, query_buf); /* * Discard \elif expression and ignore the rest until \endif. * Switch state before reading expression to ensure proper lexer * behavior. */ conditional_stack_poke(cstack, IFSTATE_IGNORED); ignore_boolean_expression(scan_state); break; case IFSTATE_FALSE: /* * Discard any query text added by the just-skipped branch. */ discard_query_text(scan_state, cstack, query_buf); /* * Have not yet found a true expression in this \if block, so this * might be the first. We have to change state before examining * the expression, or the lexer won't do the right thing. */ conditional_stack_poke(cstack, IFSTATE_TRUE); if (!is_true_boolean_expression(scan_state, "\\elif expression")) conditional_stack_poke(cstack, IFSTATE_FALSE); break; case IFSTATE_IGNORED: /* * Discard any query text added by the just-skipped branch. */ discard_query_text(scan_state, cstack, query_buf); /* * Skip expression and move on. Either the \if block already had * an active section, or whole block is being skipped. */ ignore_boolean_expression(scan_state); break; case IFSTATE_ELSE_TRUE: case IFSTATE_ELSE_FALSE: pg_log_error("\\elif: cannot occur after \\else"); success = false; break; case IFSTATE_NONE: /* no \if to elif from */ pg_log_error("\\elif: no matching \\if"); success = false; break; } return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \else -- final alternative in an \if..\endif block * * Statements within an \else branch will only be executed if * all previous \if and \elif expressions evaluated to false * and the block was not itself being ignored. */ static backslashResult exec_command_else(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf) { bool success = true; switch (conditional_stack_peek(cstack)) { case IFSTATE_TRUE: /* * Just finished active branch of this \if block. Update saved * state so we will keep whatever data was put in query_buf by the * active branch. */ save_query_text_state(scan_state, cstack, query_buf); /* Now skip the \else branch */ conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE); break; case IFSTATE_FALSE: /* * Discard any query text added by the just-skipped branch. */ discard_query_text(scan_state, cstack, query_buf); /* * We've not found any true \if or \elif expression, so execute * the \else branch. */ conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE); break; case IFSTATE_IGNORED: /* * Discard any query text added by the just-skipped branch. */ discard_query_text(scan_state, cstack, query_buf); /* * Either we previously processed the active branch of this \if, * or the whole \if block is being skipped. Either way, skip the * \else branch. */ conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE); break; case IFSTATE_ELSE_TRUE: case IFSTATE_ELSE_FALSE: pg_log_error("\\else: cannot occur after \\else"); success = false; break; case IFSTATE_NONE: /* no \if to else from */ pg_log_error("\\else: no matching \\if"); success = false; break; } return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \endif -- ends an \if...\endif block */ static backslashResult exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf) { bool success = true; switch (conditional_stack_peek(cstack)) { case IFSTATE_TRUE: case IFSTATE_ELSE_TRUE: /* Close the \if block, keeping the query text */ success = conditional_stack_pop(cstack); Assert(success); break; case IFSTATE_FALSE: case IFSTATE_IGNORED: case IFSTATE_ELSE_FALSE: /* * Discard any query text added by the just-skipped branch. */ discard_query_text(scan_state, cstack, query_buf); /* Close the \if block */ success = conditional_stack_pop(cstack); Assert(success); break; case IFSTATE_NONE: /* no \if to end */ pg_log_error("\\endif: no matching \\if"); success = false; break; } return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \l -- list databases */ static backslashResult exec_command_list(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { 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); free(pattern); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \lo_* -- large object operations */ static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd) { backslashResult status = PSQL_CMD_SKIP_LINE; bool success = true; if (active_branch) { 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) { pg_log_error("\\%s: missing required argument", cmd); success = false; } else { expand_tilde(&opt2); success = do_lo_export(opt1, opt2); } } else if (strcmp(cmd + 3, "import") == 0) { if (!opt1) { pg_log_error("\\%s: missing required argument", cmd); success = false; } else { expand_tilde(&opt1); success = do_lo_import(opt1, opt2); } } else if (strcmp(cmd + 3, "list") == 0) success = listLargeObjects(false); else if (strcmp(cmd + 3, "list+") == 0) success = listLargeObjects(true); else if (strcmp(cmd + 3, "unlink") == 0) { if (!opt1) { pg_log_error("\\%s: missing required argument", cmd); success = false; } else success = do_lo_unlink(opt1); } else status = PSQL_CMD_UNKNOWN; free(opt1); free(opt2); } else ignore_slash_options(scan_state); if (!success) status = PSQL_CMD_ERROR; return status; } /* * \o -- set query output */ static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); expand_tilde(&fname); success = setQFout(fname); free(fname); } else ignore_slash_filepipe(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \p -- print the current query buffer */ static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, PQExpBuffer previous_buf) { if (active_branch) { /* * We want to print the same thing \g would execute, but not to change * the query buffer state; so we can't use copy_previous_query(). * Also, beware of possibility that buffer pointers are NULL. */ if (query_buf && query_buf->len > 0) puts(query_buf->data); else if (previous_buf && previous_buf->len > 0) puts(previous_buf->data); else if (!pset.quiet) puts(_("Query buffer is empty.")); fflush(stdout); } return PSQL_CMD_SKIP_LINE; } /* * \password -- set user password */ static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *user = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true); char *pw1 = NULL; char *pw2 = NULL; PQExpBufferData buf; PromptInterruptContext prompt_ctx; if (user == NULL) { /* By default, the command applies to CURRENT_USER */ PGresult *res; res = PSQLexec("SELECT CURRENT_USER"); if (!res) return PSQL_CMD_ERROR; user = pg_strdup(PQgetvalue(res, 0, 0)); PQclear(res); } /* Set up to let SIGINT cancel simple_prompt_extended() */ prompt_ctx.jmpbuf = sigint_interrupt_jmp; prompt_ctx.enabled = &sigint_interrupt_enabled; prompt_ctx.canceled = false; initPQExpBuffer(&buf); printfPQExpBuffer(&buf, _("Enter new password for user \"%s\": "), user); pw1 = simple_prompt_extended(buf.data, false, &prompt_ctx); if (!prompt_ctx.canceled) pw2 = simple_prompt_extended("Enter it again: ", false, &prompt_ctx); if (prompt_ctx.canceled) { /* fail silently */ success = false; } else if (strcmp(pw1, pw2) != 0) { pg_log_error("Passwords didn't match."); success = false; } else { char *encrypted_password; encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL); if (!encrypted_password) { pg_log_info("%s", PQerrorMessage(pset.db)); success = false; } else { PGresult *res; printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ", fmtId(user)); appendStringLiteralConn(&buf, encrypted_password, pset.db); res = PSQLexec(buf.data); if (!res) success = false; else PQclear(res); PQfreemem(encrypted_password); } } free(user); free(pw1); free(pw2); termPQExpBuffer(&buf); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \prompt -- prompt and set variable */ static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { 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) { pg_log_error("\\%s: missing required argument", cmd); success = false; } else { char *result; PromptInterruptContext prompt_ctx; /* Set up to let SIGINT cancel simple_prompt_extended() */ prompt_ctx.jmpbuf = sigint_interrupt_jmp; prompt_ctx.enabled = &sigint_interrupt_enabled; prompt_ctx.canceled = false; if (arg2) { prompt_text = arg1; opt = arg2; } else opt = arg1; if (!pset.inputfile) { result = simple_prompt_extended(prompt_text, true, &prompt_ctx); } else { if (prompt_text) { fputs(prompt_text, stdout); fflush(stdout); } result = gets_fromFile(stdin); if (!result) { pg_log_error("\\%s: could not read value for variable", cmd); success = false; } } if (prompt_ctx.canceled || (result && !SetVariable(pset.vars, opt, result))) success = false; free(result); free(prompt_text); free(opt); } } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \pset -- set printing parameters */ static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { 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", "csv_fieldsep", "expanded", "fieldsep", "fieldsep_zero", "footer", "format", "linestyle", "null", "numericlocale", "pager", "pager_min_lines", "recordsep", "recordsep_zero", "tableattr", "title", "tuples_only", "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle", "xheader_width", NULL }; for (i = 0; my_list[i] != NULL; i++) { char *val = pset_value_string(my_list[i], &pset.popt); printf("%-24s %s\n", my_list[i], val); free(val); } success = true; } else success = do_pset(opt0, opt1, &pset.popt, pset.quiet); free(opt0); free(opt1); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \q or \quit -- exit psql */ static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) status = PSQL_CMD_TERMINATE; return status; } /* * \r -- reset (clear) the query buffer */ static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf) { if (active_branch) { resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); if (!pset.quiet) puts(_("Query buffer reset (cleared).")); } return PSQL_CMD_SKIP_LINE; } /* * \s -- save history in a file or show it on the screen */ static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); expand_tilde(&fname); success = printHistory(fname, pset.popt.topt.pager); if (success && !pset.quiet && fname) printf(_("Wrote history to file \"%s\".\n"), fname); if (!fname) putchar('\n'); free(fname); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \set -- set variable */ static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { 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 = pg_realloc(newval, strlen(newval) + strlen(opt) + 1); strcat(newval, opt); free(opt); } if (!SetVariable(pset.vars, opt0, newval)) success = false; free(newval); } free(opt0); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \setenv -- set environment variable */ static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { 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) { pg_log_error("\\%s: missing required argument", cmd); success = false; } else if (strchr(envvar, '=') != NULL) { pg_log_error("\\%s: environment variable name must not contain \"=\"", 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 */ setenv(envvar, envval, 1); success = true; } free(envvar); free(envval); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \sf/\sv -- show a function/view's source code */ static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_branch, const char *cmd, bool is_func) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { bool show_linenumbers = (strchr(cmd, '+') != NULL); PQExpBuffer buf; char *obj_desc; Oid obj_oid = InvalidOid; EditableObjectType eot = is_func ? EditableFunction : EditableView; buf = createPQExpBuffer(); obj_desc = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true); if (!obj_desc) { if (is_func) pg_log_error("function name is required"); else pg_log_error("view name is required"); status = PSQL_CMD_ERROR; } else if (!lookup_object_oid(eot, obj_desc, &obj_oid)) { /* error already reported */ status = PSQL_CMD_ERROR; } else if (!get_create_object_cmd(eot, obj_oid, 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 = count_lines_in_buf(buf); output = PageOutput(lineno, &(pset.popt.topt)); is_pager = true; } else { /* use previously set output file, without pager */ output = pset.queryFout; is_pager = false; } if (show_linenumbers) { /* add line numbers */ print_with_linenumbers(output, buf->data, is_func); } else { /* just send the definition to output */ fputs(buf->data, output); } if (is_pager) ClosePager(output); } free(obj_desc); destroyPQExpBuffer(buf); } else ignore_slash_whole_line(scan_state); return status; } /* * \t -- turn off table headers and row count */ static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \T -- define html attributes */ static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \timing -- enable/disable timing of queries */ static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (opt) success = ParseVariableBool(opt, "\\timing", &pset.timing); else pset.timing = !pset.timing; if (!pset.quiet) { if (pset.timing) puts(_("Timing is on.")); else puts(_("Timing is off.")); } free(opt); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \unset -- unset variable */ static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt) { pg_log_error("\\%s: missing required argument", cmd); success = false; } else if (!SetVariable(pset.vars, opt, NULL)) success = false; free(opt); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \w -- write query buffer to file */ static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch, const char *cmd, PQExpBuffer query_buf, PQExpBuffer previous_buf) { backslashResult status = PSQL_CMD_SKIP_LINE; if (active_branch) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); FILE *fd = NULL; bool is_pipe = false; if (!query_buf) { pg_log_error("no query buffer"); status = PSQL_CMD_ERROR; } else { if (!fname) { pg_log_error("\\%s: missing required argument", cmd); status = PSQL_CMD_ERROR; } else { expand_tilde(&fname); if (fname[0] == '|') { is_pipe = true; fflush(NULL); disable_sigpipe_trap(); fd = popen(&fname[1], "w"); } else { canonicalize_path(fname); fd = fopen(fname, "w"); } if (!fd) { pg_log_error("%s: %m", fname); status = PSQL_CMD_ERROR; } } } if (fd) { int result; /* * We want to print the same thing \g would execute, but not to * change the query buffer state; so we can't use * copy_previous_query(). Also, beware of possibility that buffer * pointers are NULL. */ if (query_buf && query_buf->len > 0) fprintf(fd, "%s\n", query_buf->data); else if (previous_buf && previous_buf->len > 0) fprintf(fd, "%s\n", previous_buf->data); if (is_pipe) { result = pclose(fd); if (result != 0) { pg_log_error("%s: %s", fname, wait_result_to_str(result)); status = PSQL_CMD_ERROR; } SetShellResultVariables(result); } else { result = fclose(fd); if (result == EOF) { pg_log_error("%s: %m", fname); status = PSQL_CMD_ERROR; } } } if (is_pipe) restore_sigpipe_trap(); free(fname); } else ignore_slash_filepipe(scan_state); return status; } /* * \watch -- execute a query every N seconds. * Optionally, stop after M iterations. */ static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch, PQExpBuffer query_buf, PQExpBuffer previous_buf) { bool success = true; if (active_branch) { bool have_sleep = false; bool have_iter = false; double sleep = 2; int iter = 0; /* * Parse arguments. We allow either an unlabeled interval or * "name=value", where name is from the set ('i', 'interval', 'c', * 'count'). */ while (success) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); char *valptr; char *opt_end; if (!opt) break; /* no more arguments */ valptr = strchr(opt, '='); if (valptr) { /* Labeled argument */ valptr++; if (strncmp("i=", opt, strlen("i=")) == 0 || strncmp("interval=", opt, strlen("interval=")) == 0) { if (have_sleep) { pg_log_error("\\watch: interval value is specified more than once"); success = false; } else { have_sleep = true; errno = 0; sleep = strtod(valptr, &opt_end); if (sleep < 0 || *opt_end || errno == ERANGE) { pg_log_error("\\watch: incorrect interval value \"%s\"", valptr); success = false; } } } else if (strncmp("c=", opt, strlen("c=")) == 0 || strncmp("count=", opt, strlen("count=")) == 0) { if (have_iter) { pg_log_error("\\watch: iteration count is specified more than once"); success = false; } else { have_iter = true; errno = 0; iter = strtoint(valptr, &opt_end, 10); if (iter <= 0 || *opt_end || errno == ERANGE) { pg_log_error("\\watch: incorrect iteration count \"%s\"", valptr); success = false; } } } else { pg_log_error("\\watch: unrecognized parameter \"%s\"", opt); success = false; } } else { /* Unlabeled argument: take it as interval */ if (have_sleep) { pg_log_error("\\watch: interval value is specified more than once"); success = false; } else { have_sleep = true; errno = 0; sleep = strtod(opt, &opt_end); if (sleep < 0 || *opt_end || errno == ERANGE) { pg_log_error("\\watch: incorrect interval value \"%s\"", opt); success = false; } } } free(opt); } /* If we parsed arguments successfully, do the command */ if (success) { /* If query_buf is empty, recall and execute previous query */ (void) copy_previous_query(query_buf, previous_buf); success = do_watch(query_buf, sleep, iter); } /* Reset the query buffer as though for \r */ resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \x -- set or toggle expanded table representation */ static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \z -- list table privileges (equivalent to \dp) */ static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch, const char *cmd) { bool success = true; if (active_branch) { char *pattern; bool show_system; pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); show_system = strchr(cmd, 'S') ? true : false; success = permissionsList(pattern, show_system); free(pattern); } else ignore_slash_options(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \! -- execute shell command */ static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch) { bool success = true; if (active_branch) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); success = do_shell(opt); free(opt); } else ignore_slash_whole_line(scan_state); return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; } /* * \? -- print help about backslash commands */ static backslashResult exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch) { if (active_branch) { char *opt0 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt0 || strcmp(opt0, "commands") == 0) slashUsage(pset.popt.topt.pager); else if (strcmp(opt0, "options") == 0) usage(pset.popt.topt.pager); else if (strcmp(opt0, "variables") == 0) helpVariables(pset.popt.topt.pager); else slashUsage(pset.popt.topt.pager); free(opt0); } else ignore_slash_options(scan_state); return PSQL_CMD_SKIP_LINE; } /* * Read and interpret an argument to the \connect slash command. * * Returns a malloc'd string, or NULL if no/empty argument. */ 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) { free(result); return NULL; } return result; } /* * Read a boolean expression, return it as a PQExpBuffer string. * * Note: anything more or less than one token will certainly fail to be * parsed by ParseVariableBool, so we don't worry about complaining here. * This routine's return data structure will need to be rethought anyway * to support likely future extensions such as "\if defined VARNAME". */ static PQExpBuffer gather_boolean_expression(PsqlScanState scan_state) { PQExpBuffer exp_buf = createPQExpBuffer(); int num_options = 0; char *value; /* collect all arguments for the conditional command into exp_buf */ while ((value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false)) != NULL) { /* add spaces between tokens */ if (num_options > 0) appendPQExpBufferChar(exp_buf, ' '); appendPQExpBufferStr(exp_buf, value); num_options++; free(value); } return exp_buf; } /* * Read a boolean expression, return true if the expression * was a valid boolean expression that evaluated to true. * Otherwise return false. * * Note: conditional stack's top state must be active, else lexer will * fail to expand variables and backticks. */ static bool is_true_boolean_expression(PsqlScanState scan_state, const char *name) { PQExpBuffer buf = gather_boolean_expression(scan_state); bool value = false; bool success = ParseVariableBool(buf->data, name, &value); destroyPQExpBuffer(buf); return success && value; } /* * Read a boolean expression, but do nothing with it. * * Note: conditional stack's top state must be INACTIVE, else lexer will * expand variables and backticks, which we do not want here. */ static void ignore_boolean_expression(PsqlScanState scan_state) { PQExpBuffer buf = gather_boolean_expression(scan_state); destroyPQExpBuffer(buf); } /* * Read and discard "normal" slash command options. * * This should be used for inactive-branch processing of any slash command * that eats one or more OT_NORMAL, OT_SQLID, or OT_SQLIDHACK parameters. * We don't need to worry about exactly how many it would eat, since the * cleanup logic in HandleSlashCmds would silently discard any extras anyway. */ static void ignore_slash_options(PsqlScanState scan_state) { char *arg; while ((arg = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false)) != NULL) free(arg); } /* * Read and discard FILEPIPE slash command argument. * * This *MUST* be used for inactive-branch processing of any slash command * that takes an OT_FILEPIPE option. Otherwise we might consume a different * amount of option text in active and inactive cases. */ static void ignore_slash_filepipe(PsqlScanState scan_state) { char *arg = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); free(arg); } /* * Read and discard whole-line slash command argument. * * This *MUST* be used for inactive-branch processing of any slash command * that takes an OT_WHOLE_LINE option. Otherwise we might consume a different * amount of option text in active and inactive cases. */ static void ignore_slash_whole_line(PsqlScanState scan_state) { char *arg = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); free(arg); } /* * Return true if the command given is a branching command. */ static bool is_branching_command(const char *cmd) { return (strcmp(cmd, "if") == 0 || strcmp(cmd, "elif") == 0 || strcmp(cmd, "else") == 0 || strcmp(cmd, "endif") == 0); } /* * Prepare to possibly restore query buffer to its current state * (cf. discard_query_text). * * We need to remember the length of the query buffer, and the lexer's * notion of the parenthesis nesting depth. */ static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf) { if (query_buf) conditional_stack_set_query_len(cstack, query_buf->len); conditional_stack_set_paren_depth(cstack, psql_scan_get_paren_depth(scan_state)); } /* * Discard any query text absorbed during an inactive conditional branch. * * We must discard data that was appended to query_buf during an inactive * \if branch. We don't have to do anything there if there's no query_buf. * * Also, reset the lexer state to the same paren depth there was before. * (The rest of its state doesn't need attention, since we could not be * inside a comment or literal or partial token.) */ static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack, PQExpBuffer query_buf) { if (query_buf) { int new_len = conditional_stack_get_query_len(cstack); Assert(new_len >= 0 && new_len <= query_buf->len); query_buf->len = new_len; query_buf->data[new_len] = '\0'; } psql_scan_set_paren_depth(scan_state, conditional_stack_get_paren_depth(cstack)); } /* * If query_buf is empty, copy previous_buf into it. * * This is used by various slash commands for which re-execution of a * previous query is a common usage. For convenience, we allow the * case of query_buf == NULL (and do nothing). * * Returns "true" if the previous query was copied into the query * buffer, else "false". */ static bool copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf) { if (query_buf && query_buf->len == 0) { appendPQExpBufferStr(query_buf, previous_buf->data); return true; } return false; } /* * 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. * If 'canceled' is provided, *canceled will be set to true if the prompt * is canceled via SIGINT, and to false otherwise. */ static char * prompt_for_password(const char *username, bool *canceled) { char *result; PromptInterruptContext prompt_ctx; /* Set up to let SIGINT cancel simple_prompt_extended() */ prompt_ctx.jmpbuf = sigint_interrupt_jmp; prompt_ctx.enabled = &sigint_interrupt_enabled; prompt_ctx.canceled = false; if (username == NULL || username[0] == '\0') result = simple_prompt_extended("Password: ", false, &prompt_ctx); else { char *prompt_text; prompt_text = psprintf(_("Password for user %s: "), username); result = simple_prompt_extended(prompt_text, false, &prompt_ctx); free(prompt_text); } if (canceled) *canceled = prompt_ctx.canceled; 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 we are told to re-use * parameters, parameters from the previous connection are used where the * command's own options do not supply a value. Otherwise, libpq defaults * are used. * * In interactive mode, if connection fails with the given parameters, * the old connection will be kept. */ static bool do_connect(enum trivalue reuse_previous_specification, char *dbname, char *user, char *host, char *port) { PGconn *o_conn = pset.db, *n_conn = NULL; PQconninfoOption *cinfo; int nconnopts = 0; bool same_host = false; char *password = NULL; char *client_encoding; bool success = true; bool keep_password = true; bool has_connection_string; bool reuse_previous; has_connection_string = dbname ? recognized_connection_string(dbname) : false; /* Complain if we have additional arguments after a connection string. */ if (has_connection_string && (user || host || port)) { pg_log_error("Do not give user, host, or port separately when using a connection string"); return false; } switch (reuse_previous_specification) { case TRI_YES: reuse_previous = true; break; case TRI_NO: reuse_previous = false; break; default: reuse_previous = !has_connection_string; break; } /* * If we intend to re-use connection parameters, collect them out of the * old connection, then replace individual values as necessary. (We may * need to resort to looking at pset.dead_conn, if the connection died * previously.) Otherwise, obtain a PQconninfoOption array containing * libpq's defaults, and modify that. Note this function assumes that * PQconninfo, PQconndefaults, and PQconninfoParse will all produce arrays * containing the same options in the same order. */ if (reuse_previous) { if (o_conn) cinfo = PQconninfo(o_conn); else if (pset.dead_conn) cinfo = PQconninfo(pset.dead_conn); else { /* This is reachable after a non-interactive \connect failure */ pg_log_error("No database connection exists to re-use parameters from"); return false; } } else cinfo = PQconndefaults(); if (cinfo) { if (has_connection_string) { /* Parse the connstring and insert values into cinfo */ PQconninfoOption *replcinfo; char *errmsg; replcinfo = PQconninfoParse(dbname, &errmsg); if (replcinfo) { PQconninfoOption *ci; PQconninfoOption *replci; bool have_password = false; for (ci = cinfo, replci = replcinfo; ci->keyword && replci->keyword; ci++, replci++) { Assert(strcmp(ci->keyword, replci->keyword) == 0); /* Insert value from connstring if one was provided */ if (replci->val) { /* * We know that both val strings were allocated by * libpq, so the least messy way to avoid memory leaks * is to swap them. */ char *swap = replci->val; replci->val = ci->val; ci->val = swap; /* * Check whether connstring provides options affecting * password re-use. While any change in user, host, * hostaddr, or port causes us to ignore the old * connection's password, we don't force that for * dbname, since passwords aren't database-specific. */ if (replci->val == NULL || strcmp(ci->val, replci->val) != 0) { if (strcmp(replci->keyword, "user") == 0 || strcmp(replci->keyword, "host") == 0 || strcmp(replci->keyword, "hostaddr") == 0 || strcmp(replci->keyword, "port") == 0) keep_password = false; } /* Also note whether connstring contains a password. */ if (strcmp(replci->keyword, "password") == 0) have_password = true; } else if (!reuse_previous) { /* * When we have a connstring and are not re-using * parameters, swap *all* entries, even those not set * by the connstring. This avoids absorbing * environment-dependent defaults from the result of * PQconndefaults(). We don't want to do that because * they'd override service-file entries if the * connstring specifies a service parameter, whereas * the priority should be the other way around. libpq * can certainly recompute any defaults we don't pass * here. (In this situation, it's a bit wasteful to * have called PQconndefaults() at all, but not doing * so would require yet another major code path here.) */ replci->val = ci->val; ci->val = NULL; } } Assert(ci->keyword == NULL && replci->keyword == NULL); /* While here, determine how many option slots there are */ nconnopts = ci - cinfo; PQconninfoFree(replcinfo); /* * If the connstring contains a password, tell the loop below * that we may use it, regardless of other settings (i.e., * cinfo's password is no longer an "old" password). */ if (have_password) keep_password = true; /* Don't let code below try to inject dbname into params. */ dbname = NULL; } else { /* PQconninfoParse failed */ if (errmsg) { pg_log_error("%s", errmsg); PQfreemem(errmsg); } else pg_log_error("out of memory"); success = false; } } else { /* * If dbname isn't a connection string, then we'll inject it and * the other parameters into the keyword array below. (We can't * easily insert them into the cinfo array because of memory * management issues: PQconninfoFree would misbehave on Windows.) * However, to avoid dependencies on the order in which parameters * appear in the array, make a preliminary scan to set * keep_password and same_host correctly. * * While any change in user, host, or port causes us to ignore the * old connection's password, we don't force that for dbname, * since passwords aren't database-specific. */ PQconninfoOption *ci; for (ci = cinfo; ci->keyword; ci++) { if (user && strcmp(ci->keyword, "user") == 0) { if (!(ci->val && strcmp(user, ci->val) == 0)) keep_password = false; } else if (host && strcmp(ci->keyword, "host") == 0) { if (ci->val && strcmp(host, ci->val) == 0) same_host = true; else keep_password = false; } else if (port && strcmp(ci->keyword, "port") == 0) { if (!(ci->val && strcmp(port, ci->val) == 0)) keep_password = false; } } /* While here, determine how many option slots there are */ nconnopts = ci - cinfo; } } else { /* We failed to create the cinfo structure */ pg_log_error("out of memory"); success = false; } /* * 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 * etc have 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 && success) { bool canceled = false; /* * If a connstring or URI is provided, we don't know which username * will be used, since we haven't dug that out of the connstring. * Don't risk issuing a misleading prompt. As in startup.c, it does * not seem worth working harder, since this getPassword setting is * normally only used in noninteractive cases. */ password = prompt_for_password(has_connection_string ? NULL : user, &canceled); success = !canceled; } /* * Consider whether to force client_encoding to "auto" (overriding * anything in the connection string). We do so if we have a terminal * connection and there is no PGCLIENTENCODING environment setting. */ if (pset.notty || getenv("PGCLIENTENCODING")) client_encoding = NULL; else client_encoding = "auto"; /* Loop till we have a connection or fail, which we might've already */ while (success) { const char **keywords = pg_malloc((nconnopts + 1) * sizeof(*keywords)); const char **values = pg_malloc((nconnopts + 1) * sizeof(*values)); int paramnum = 0; PQconninfoOption *ci; /* * Copy non-default settings into the PQconnectdbParams parameter * arrays; but inject any values specified old-style, as well as any * interactively-obtained password, and a couple of fields we want to * set forcibly. * * If you change this code, see also the initial-connection code in * main(). */ for (ci = cinfo; ci->keyword; ci++) { keywords[paramnum] = ci->keyword; if (dbname && strcmp(ci->keyword, "dbname") == 0) values[paramnum++] = dbname; else if (user && strcmp(ci->keyword, "user") == 0) values[paramnum++] = user; else if (host && strcmp(ci->keyword, "host") == 0) values[paramnum++] = host; else if (host && !same_host && strcmp(ci->keyword, "hostaddr") == 0) { /* If we're changing the host value, drop any old hostaddr */ values[paramnum++] = NULL; } else if (port && strcmp(ci->keyword, "port") == 0) values[paramnum++] = port; /* If !keep_password, we unconditionally drop old password */ else if ((password || !keep_password) && strcmp(ci->keyword, "password") == 0) values[paramnum++] = password; else if (strcmp(ci->keyword, "fallback_application_name") == 0) values[paramnum++] = pset.progname; else if (client_encoding && strcmp(ci->keyword, "client_encoding") == 0) values[paramnum++] = client_encoding; else if (ci->val) values[paramnum++] = ci->val; /* else, don't bother making libpq parse this keyword */ } /* add array terminator */ keywords[paramnum] = NULL; values[paramnum] = NULL; /* Note we do not want libpq to re-expand the dbname parameter */ n_conn = PQconnectdbParams(keywords, values, false); pg_free(keywords); pg_free(values); 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) { bool canceled = false; /* * Prompt for password using the username we actually connected * with --- it might've come out of "dbname" rather than "user". */ password = prompt_for_password(PQuser(n_conn), &canceled); PQfinish(n_conn); n_conn = NULL; success = !canceled; continue; } /* * We'll report the error below ... unless n_conn is NULL, indicating * that libpq didn't have enough memory to make a PGconn. */ if (n_conn == NULL) pg_log_error("out of memory"); success = false; } /* end retry loop */ /* Release locally allocated data, whether we succeeded or not */ pg_free(password); PQconninfoFree(cinfo); if (!success) { /* * 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) { if (n_conn) { pg_log_info("%s", PQerrorMessage(n_conn)); PQfinish(n_conn); } /* pset.db is left unmodified */ if (o_conn) pg_log_info("Previous connection kept"); } else { if (n_conn) { pg_log_error("\\connect: %s", PQerrorMessage(n_conn)); PQfinish(n_conn); } if (o_conn) { /* * Transition to having no connection. * * Unlike CheckConnection(), we close the old connection * immediately to prevent its parameters from being re-used. * This is so that a script cannot accidentally reuse * parameters it did not expect to. Otherwise, the state * cleanup should be the same as in CheckConnection(). */ PQfinish(o_conn); pset.db = NULL; ResetCancelConn(); UnsyncVariables(); } /* On the same reasoning, release any dead_conn to prevent reuse */ if (pset.dead_conn) { PQfinish(pset.dead_conn); pset.dead_conn = NULL; } } return false; } /* * Replace the old connection with the new one, and update * connection-dependent variables. Keep the resynchronization logic in * sync with CheckConnection(). */ 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 (!o_conn || param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) || param_is_newly_set(PQport(o_conn), PQport(pset.db))) { char *connhost = PQhost(pset.db); char *hostaddr = PQhostaddr(pset.db); if (is_unixsock_path(connhost)) { /* hostaddr overrides connhost */ if (hostaddr && *hostaddr) printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"), PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db)); else 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), connhost, PQport(pset.db)); } else { if (hostaddr && *hostaddr && strcmp(connhost, hostaddr) != 0) printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"), PQdb(pset.db), PQuser(pset.db), connhost, hostaddr, 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), connhost, PQport(pset.db)); } } else printf(_("You are now connected to database \"%s\" as user \"%s\".\n"), PQdb(pset.db), PQuser(pset.db)); } /* Drop no-longer-needed connection(s) */ if (o_conn) PQfinish(o_conn); if (pset.dead_conn) { PQfinish(pset.dead_conn); pset.dead_conn = NULL; } return true; } void connection_warnings(bool in_startup) { if (!pset.quiet && !pset.notty) { int client_ver = PG_VERSION_NUM; char cverbuf[32]; char sverbuf[32]; if (pset.sversion != client_ver) { const char *server_version; /* Try to get full text form, might include "devel" etc */ server_version = PQparameterStatus(pset.db, "server_version"); /* Otherwise fall back on pset.sversion */ if (!server_version) { formatPGVersionNumber(pset.sversion, true, sverbuf, sizeof(sverbuf)); server_version = sverbuf; } 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); /* * Warn if server's major version is newer than ours, or if server * predates our support cutoff (currently 9.2). */ if (pset.sversion / 100 > client_ver / 100 || pset.sversion < 90200) printf(_("WARNING: %s major version %s, server major version %s.\n" " Some psql features might not work.\n"), pset.progname, formatPGVersionNumber(client_ver, false, cverbuf, sizeof(cverbuf)), formatPGVersionNumber(pset.sversion, false, sverbuf, sizeof(sverbuf))); #ifdef WIN32 if (in_startup) checkWin32Codepage(); #endif printSSLInfo(); printGSSInfo(); } } /* * printSSLInfo * * Prints information about the current SSL connection, if SSL is in use */ static void printSSLInfo(void) { const char *protocol; const char *cipher; const char *compression; if (!PQsslInUse(pset.db)) return; /* no SSL */ protocol = PQsslAttribute(pset.db, "protocol"); cipher = PQsslAttribute(pset.db, "cipher"); compression = PQsslAttribute(pset.db, "compression"); printf(_("SSL connection (protocol: %s, cipher: %s, compression: %s)\n"), protocol ? protocol : _("unknown"), cipher ? cipher : _("unknown"), (compression && strcmp(compression, "off") != 0) ? _("on") : _("off")); } /* * printGSSInfo * * Prints information about the current GSSAPI connection, if GSSAPI encryption is in use */ static void printGSSInfo(void) { if (!PQgssEncInUse(pset.db)) return; /* no GSSAPI encryption in use */ printf(_("GSSAPI-encrypted connection\n")); } /* * 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) { char vbuf[32]; const char *server_version; /* 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)); /* this bit should match connection_warnings(): */ /* Try to get full text form of version, might include "devel" etc */ server_version = PQparameterStatus(pset.db, "server_version"); /* Otherwise fall back on pset.sversion */ if (!server_version) { formatPGVersionNumber(pset.sversion, true, vbuf, sizeof(vbuf)); server_version = vbuf; } SetVariable(pset.vars, "SERVER_VERSION_NAME", server_version); snprintf(vbuf, sizeof(vbuf), "%d", pset.sversion); SetVariable(pset.vars, "SERVER_VERSION_NUM", vbuf); /* send stuff to it, too */ PQsetErrorVerbosity(pset.db, pset.verbosity); PQsetErrorContextVisibility(pset.db, pset.show_context); } /* * 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); SetVariable(pset.vars, "SERVER_VERSION_NAME", NULL); SetVariable(pset.vars, "SERVER_VERSION_NUM", NULL); } /* * helper for do_edit(): actually invoke the editor * * Returns true on success, false if we failed to invoke the editor or * it returned nonzero status. (An error message is printed for failed- * to-invoke cases, but not if the editor returns nonzero status.) */ 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) { pg_log_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number"); 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 fflush(NULL); result = system(sys); if (result == -1) pg_log_error("could not start editor \"%s\"", editorName); else if (result == 127) pg_log_error("could not start /bin/sh"); free(sys); return result == 0; } /* * do_edit -- handler for \e * * If you do not specify a filename, the current query buffer will be copied * into a temporary file. * * After this function is done, the resulting file will be copied back into the * query buffer. As an exception to this, the query buffer will be emptied * if the file was not modified (or the editor failed) and the caller passes * "discard_on_quit" = true. * * If "edited" isn't NULL, *edited will be set to true if the query buffer * is successfully replaced. */ static bool do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool discard_on_quit, 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) { pg_log_error("could not locate temporary directory: %s", !ret ? strerror(errno) : ""); return false; } #endif /* * 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. */ #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) { pg_log_error("could not open temporary file \"%s\": %m", fname); error = true; } else { unsigned int ql = query_buf->len; /* force newline-termination of what we send to editor */ if (ql > 0 && query_buf->data[ql - 1] != '\n') { appendPQExpBufferChar(query_buf, '\n'); ql++; } if (fwrite(query_buf->data, 1, ql, stream) != ql) { pg_log_error("%s: %m", fname); if (fclose(stream) != 0) pg_log_error("%s: %m", fname); if (remove(fname) != 0) pg_log_error("%s: %m", fname); error = true; } else if (fclose(stream) != 0) { pg_log_error("%s: %m", fname); if (remove(fname) != 0) pg_log_error("%s: %m", fname); error = true; } else { struct utimbuf ut; /* * Try to set the file modification time of the temporary file * a few seconds in the past. Otherwise, the low granularity * (one second, or even worse on some filesystems) that we can * portably measure with stat(2) could lead us to not * recognize a modification, if the user typed very quickly. * * This is a rather unlikely race condition, so don't error * out if the utime(2) call fails --- that would make the cure * worse than the disease. */ ut.modtime = ut.actime = time(NULL) - 2; (void) utime(fname, &ut); } } } if (!error && stat(fname, &before) != 0) { pg_log_error("%s: %m", fname); error = true; } /* call editor */ if (!error) error = !editFile(fname, lineno); if (!error && stat(fname, &after) != 0) { pg_log_error("%s: %m", fname); error = true; } /* file was edited if the size or modification time has changed */ if (!error && (before.st_size != after.st_size || before.st_mtime != after.st_mtime)) { stream = fopen(fname, PG_BINARY_R); if (!stream) { pg_log_error("%s: %m", fname); 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)) { pg_log_error("%s: %m", fname); error = true; resetPQExpBuffer(query_buf); } else if (edited) { *edited = true; } fclose(stream); } } else { /* * If the file was not modified, and the caller requested it, discard * the query buffer. */ if (discard_on_quit) resetPQExpBuffer(query_buf); } /* remove temp file */ if (!filename_arg) { if (remove(fname) == -1) { pg_log_error("%s: %m", fname); 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 use_relative_path) { FILE *fd; int result; char *oldfilename; char relpath[MAXPGPATH]; 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 component * of the current script to this pathname. */ if (use_relative_path && pset.inputfile && !is_absolute_path(filename) && !has_drive_prefix(filename)) { strlcpy(relpath, pset.inputfile, sizeof(relpath)); get_parent_directory(relpath); join_path_components(relpath, relpath, filename); canonicalize_path(relpath); filename = relpath; } fd = fopen(filename, PG_BINARY_R); if (!fd) { pg_log_error("%s: %m", filename); return EXIT_FAILURE; } } else { fd = stdin; filename = ""; /* for future error messages */ } oldfilename = pset.inputfile; pset.inputfile = filename; pg_logging_config(pset.inputfile ? 0 : PG_LOG_FLAG_TERSE); result = MainLoop(fd); if (fd != stdin) fclose(fd); pset.inputfile = oldfilename; pg_logging_config(pset.inputfile ? 0 : PG_LOG_FLAG_TERSE); return result; } static const char * _align2string(enum printFormat in) { switch (in) { case PRINT_NOTHING: return "nothing"; break; case PRINT_ALIGNED: return "aligned"; break; case PRINT_ASCIIDOC: return "asciidoc"; break; case PRINT_CSV: return "csv"; break; case PRINT_HTML: return "html"; break; case PRINT_LATEX: return "latex"; break; case PRINT_LATEX_LONGTABLE: return "latex-longtable"; break; case PRINT_TROFF_MS: return "troff-ms"; break; case PRINT_UNALIGNED: return "unaligned"; break; case PRINT_WRAPPED: return "wrapped"; break; } return "unknown"; } /* * Parse entered Unicode linestyle. If ok, update *linestyle and return * true, else return false. */ static bool set_unicode_line_style(const char *value, size_t vallen, unicode_linestyle *linestyle) { if (pg_strncasecmp("single", value, vallen) == 0) *linestyle = UNICODE_LINESTYLE_SINGLE; else if (pg_strncasecmp("double", value, vallen) == 0) *linestyle = UNICODE_LINESTYLE_DOUBLE; else return false; return true; } static const char * _unicode_linestyle2string(int linestyle) { switch (linestyle) { case UNICODE_LINESTYLE_SINGLE: return "single"; break; case UNICODE_LINESTYLE_DOUBLE: return "double"; break; } return "unknown"; } /* * do_pset * * Performs the assignment "param = value", where value could be NULL; * for some params that has an effect such as inversion, for others * it does nothing. * * Adjusts the state of the formatting options at *popt. (In practice that * is always pset.popt, but maybe someday it could be different.) * * If successful and quiet is false, then invokes printPsetInfo() to report * the change. * * Returns true if successful, else false (eg for invalid param or value). */ bool do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) { size_t vallen = 0; Assert(param != NULL); if (value) vallen = strlen(value); /* set format */ if (strcmp(param, "format") == 0) { static const struct fmt { const char *name; enum printFormat number; } formats[] = { /* remember to update error message below when adding more */ {"aligned", PRINT_ALIGNED}, {"asciidoc", PRINT_ASCIIDOC}, {"csv", PRINT_CSV}, {"html", PRINT_HTML}, {"latex", PRINT_LATEX}, {"troff-ms", PRINT_TROFF_MS}, {"unaligned", PRINT_UNALIGNED}, {"wrapped", PRINT_WRAPPED} }; if (!value) ; else { int match_pos = -1; for (int i = 0; i < lengthof(formats); i++) { if (pg_strncasecmp(formats[i].name, value, vallen) == 0) { if (match_pos < 0) match_pos = i; else { pg_log_error("\\pset: ambiguous abbreviation \"%s\" matches both \"%s\" and \"%s\"", value, formats[match_pos].name, formats[i].name); return false; } } } if (match_pos >= 0) popt->topt.format = formats[match_pos].number; else if (pg_strncasecmp("latex-longtable", value, vallen) == 0) { /* * We must treat latex-longtable specially because latex is a * prefix of it; if both were in the table above, we'd think * "latex" is ambiguous. */ popt->topt.format = PRINT_LATEX_LONGTABLE; } else { pg_log_error("\\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped"); return false; } } } /* set table line style */ else if (strcmp(param, "linestyle") == 0) { if (!value) ; else if (pg_strncasecmp("ascii", value, vallen) == 0) popt->topt.line_style = &pg_asciiformat; else if (pg_strncasecmp("old-ascii", value, vallen) == 0) popt->topt.line_style = &pg_asciiformat_old; else if (pg_strncasecmp("unicode", value, vallen) == 0) popt->topt.line_style = &pg_utf8format; else { pg_log_error("\\pset: allowed line styles are ascii, old-ascii, unicode"); return false; } } /* set unicode border line style */ else if (strcmp(param, "unicode_border_linestyle") == 0) { if (!value) ; else if (set_unicode_line_style(value, vallen, &popt->topt.unicode_border_linestyle)) refresh_utf8format(&(popt->topt)); else { pg_log_error("\\pset: allowed Unicode border line styles are single, double"); return false; } } /* set unicode column line style */ else if (strcmp(param, "unicode_column_linestyle") == 0) { if (!value) ; else if (set_unicode_line_style(value, vallen, &popt->topt.unicode_column_linestyle)) refresh_utf8format(&(popt->topt)); else { pg_log_error("\\pset: allowed Unicode column line styles are single, double"); return false; } } /* set unicode header line style */ else if (strcmp(param, "unicode_header_linestyle") == 0) { if (!value) ; else if (set_unicode_line_style(value, vallen, &popt->topt.unicode_header_linestyle)) refresh_utf8format(&(popt->topt)); else { pg_log_error("\\pset: allowed Unicode header line styles are single, double"); return false; } } /* set border style/width */ else if (strcmp(param, "border") == 0) { if (value) popt->topt.border = atoi(value); } /* set expanded/vertical mode */ else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0) { if (value && pg_strcasecmp(value, "auto") == 0) popt->topt.expanded = 2; else if (value) { bool on_off; if (ParseVariableBool(value, NULL, &on_off)) popt->topt.expanded = on_off ? 1 : 0; else { PsqlVarEnumError(param, value, "on, off, auto"); return false; } } else popt->topt.expanded = !popt->topt.expanded; } /* header line width in expanded mode */ else if (strcmp(param, "xheader_width") == 0) { if (! value) ; else if (pg_strcasecmp(value, "full") == 0) popt->topt.expanded_header_width_type = PRINT_XHEADER_FULL; else if (pg_strcasecmp(value, "column") == 0) popt->topt.expanded_header_width_type = PRINT_XHEADER_COLUMN; else if (pg_strcasecmp(value, "page") == 0) popt->topt.expanded_header_width_type = PRINT_XHEADER_PAGE; else { popt->topt.expanded_header_width_type = PRINT_XHEADER_EXACT_WIDTH; popt->topt.expanded_header_exact_width = atoi(value); if (popt->topt.expanded_header_exact_width == 0) { pg_log_error("\\pset: allowed xheader_width values are full (default), column, page, or a number specifying the exact width."); return false; } } } /* field separator for CSV format */ else if (strcmp(param, "csv_fieldsep") == 0) { if (value) { /* CSV separator has to be a one-byte character */ if (strlen(value) != 1) { pg_log_error("\\pset: csv_fieldsep must be a single one-byte character"); return false; } if (value[0] == '"' || value[0] == '\n' || value[0] == '\r') { pg_log_error("\\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return"); return false; } popt->topt.csvFieldSep[0] = value[0]; } } /* locale-aware numeric output */ else if (strcmp(param, "numericlocale") == 0) { if (value) return ParseVariableBool(value, param, &popt->topt.numericLocale); else popt->topt.numericLocale = !popt->topt.numericLocale; } /* null display */ else if (strcmp(param, "null") == 0) { if (value) { free(popt->nullPrint); popt->nullPrint = pg_strdup(value); } } /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { if (value) { free(popt->topt.fieldSep.separator); popt->topt.fieldSep.separator = pg_strdup(value); popt->topt.fieldSep.separator_zero = false; } } else if (strcmp(param, "fieldsep_zero") == 0) { free(popt->topt.fieldSep.separator); popt->topt.fieldSep.separator = NULL; popt->topt.fieldSep.separator_zero = true; } /* record separator for unaligned text */ else if (strcmp(param, "recordsep") == 0) { if (value) { free(popt->topt.recordSep.separator); popt->topt.recordSep.separator = pg_strdup(value); popt->topt.recordSep.separator_zero = false; } } else if (strcmp(param, "recordsep_zero") == 0) { free(popt->topt.recordSep.separator); popt->topt.recordSep.separator = NULL; popt->topt.recordSep.separator_zero = true; } /* toggle between full and tuples-only format */ else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0) { if (value) return ParseVariableBool(value, param, &popt->topt.tuples_only); else popt->topt.tuples_only = !popt->topt.tuples_only; } /* set title override */ else if (strcmp(param, "C") == 0 || strcmp(param, "title") == 0) { free(popt->title); if (!value) popt->title = NULL; else popt->title = pg_strdup(value); } /* 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); } /* toggle use of pager */ else if (strcmp(param, "pager") == 0) { if (value && pg_strcasecmp(value, "always") == 0) popt->topt.pager = 2; else if (value) { bool on_off; if (!ParseVariableBool(value, NULL, &on_off)) { PsqlVarEnumError(param, value, "on, off, always"); return false; } popt->topt.pager = on_off ? 1 : 0; } else if (popt->topt.pager == 1) popt->topt.pager = 0; else popt->topt.pager = 1; } /* set minimum lines for pager use */ else if (strcmp(param, "pager_min_lines") == 0) { if (value) popt->topt.pager_min_lines = atoi(value); } /* disable "(x rows)" footer */ else if (strcmp(param, "footer") == 0) { if (value) return ParseVariableBool(value, param, &popt->topt.default_footer); else popt->topt.default_footer = !popt->topt.default_footer; } /* set border style/width */ else if (strcmp(param, "columns") == 0) { if (value) popt->topt.columns = atoi(value); } else { pg_log_error("\\pset: unknown option: %s", param); return false; } if (!quiet) printPsetInfo(param, &pset.popt); return true; } /* * printPsetInfo: print the state of the "param" formatting parameter in popt. */ static bool printPsetInfo(const char *param, printQueryOpt *popt) { Assert(param != NULL); /* show border style/width */ if (strcmp(param, "border") == 0) printf(_("Border style is %d.\n"), popt->topt.border); /* show the target width for the wrapped format */ else if (strcmp(param, "columns") == 0) { if (!popt->topt.columns) printf(_("Target width is unset.\n")); else printf(_("Target width is %d.\n"), popt->topt.columns); } /* show expanded/vertical mode */ else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0) { if (popt->topt.expanded == 1) printf(_("Expanded display is on.\n")); else if (popt->topt.expanded == 2) printf(_("Expanded display is used automatically.\n")); else printf(_("Expanded display is off.\n")); } /* show xheader width value */ else if (strcmp(param, "xheader_width") == 0) { if (popt->topt.expanded_header_width_type == PRINT_XHEADER_FULL) printf(_("Expanded header width is 'full'.\n")); else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_COLUMN) printf(_("Expanded header width is 'column'.\n")); else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_PAGE) printf(_("Expanded header width is 'page'.\n")); else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_EXACT_WIDTH) printf(_("Expanded header width is %d.\n"), popt->topt.expanded_header_exact_width); } /* show field separator for CSV format */ else if (strcmp(param, "csv_fieldsep") == 0) { printf(_("Field separator for CSV is \"%s\".\n"), popt->topt.csvFieldSep); } /* show field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { if (popt->topt.fieldSep.separator_zero) printf(_("Field separator is zero byte.\n")); else printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep.separator); } else if (strcmp(param, "fieldsep_zero") == 0) { printf(_("Field separator is zero byte.\n")); } /* show disable "(x rows)" footer */ else if (strcmp(param, "footer") == 0) { if (popt->topt.default_footer) printf(_("Default footer is on.\n")); else printf(_("Default footer is off.\n")); } /* show format */ else if (strcmp(param, "format") == 0) { printf(_("Output format is %s.\n"), _align2string(popt->topt.format)); } /* show table line style */ else if (strcmp(param, "linestyle") == 0) { printf(_("Line style is %s.\n"), get_line_style(&popt->topt)->name); } /* show null display */ else if (strcmp(param, "null") == 0) { printf(_("Null display is \"%s\".\n"), popt->nullPrint ? popt->nullPrint : ""); } /* show locale-aware numeric output */ else if (strcmp(param, "numericlocale") == 0) { if (popt->topt.numericLocale) printf(_("Locale-adjusted numeric output is on.\n")); else printf(_("Locale-adjusted numeric output is off.\n")); } /* show toggle use of pager */ else if (strcmp(param, "pager") == 0) { if (popt->topt.pager == 1) printf(_("Pager is used for long output.\n")); else if (popt->topt.pager == 2) printf(_("Pager is always used.\n")); else printf(_("Pager usage is off.\n")); } /* show minimum lines for pager use */ else if (strcmp(param, "pager_min_lines") == 0) { printf(ngettext("Pager won't be used for less than %d line.\n", "Pager won't be used for less than %d lines.\n", popt->topt.pager_min_lines), popt->topt.pager_min_lines); } /* show record separator for unaligned text */ else if (strcmp(param, "recordsep") == 0) { if (popt->topt.recordSep.separator_zero) printf(_("Record separator is zero byte.\n")); else if (strcmp(popt->topt.recordSep.separator, "\n") == 0) printf(_("Record separator is .\n")); else printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep.separator); } else if (strcmp(param, "recordsep_zero") == 0) { printf(_("Record separator is zero byte.\n")); } /* show HTML table tag options */ else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0) { if (popt->topt.tableAttr) printf(_("Table attributes are \"%s\".\n"), popt->topt.tableAttr); else printf(_("Table attributes unset.\n")); } /* show title override */ else if (strcmp(param, "C") == 0 || strcmp(param, "title") == 0) { if (popt->title) printf(_("Title is \"%s\".\n"), popt->title); else printf(_("Title is unset.\n")); } /* show toggle between full and tuples-only format */ else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0) { if (popt->topt.tuples_only) printf(_("Tuples only is on.\n")); else printf(_("Tuples only is off.\n")); } /* Unicode style formatting */ else if (strcmp(param, "unicode_border_linestyle") == 0) { printf(_("Unicode border line style is \"%s\".\n"), _unicode_linestyle2string(popt->topt.unicode_border_linestyle)); } else if (strcmp(param, "unicode_column_linestyle") == 0) { printf(_("Unicode column line style is \"%s\".\n"), _unicode_linestyle2string(popt->topt.unicode_column_linestyle)); } else if (strcmp(param, "unicode_header_linestyle") == 0) { printf(_("Unicode header line style is \"%s\".\n"), _unicode_linestyle2string(popt->topt.unicode_header_linestyle)); } else { pg_log_error("\\pset: unknown option: %s", param); return false; } return true; } /* * savePsetInfo: make a malloc'd copy of the data in *popt. * * Possibly this should be somewhere else, but it's a bit specific to psql. */ printQueryOpt * savePsetInfo(const printQueryOpt *popt) { printQueryOpt *save; save = (printQueryOpt *) pg_malloc(sizeof(printQueryOpt)); /* Flat-copy all the scalar fields, then duplicate sub-structures. */ memcpy(save, popt, sizeof(printQueryOpt)); /* topt.line_style points to const data that need not be duplicated */ if (popt->topt.fieldSep.separator) save->topt.fieldSep.separator = pg_strdup(popt->topt.fieldSep.separator); if (popt->topt.recordSep.separator) save->topt.recordSep.separator = pg_strdup(popt->topt.recordSep.separator); if (popt->topt.tableAttr) save->topt.tableAttr = pg_strdup(popt->topt.tableAttr); if (popt->nullPrint) save->nullPrint = pg_strdup(popt->nullPrint); if (popt->title) save->title = pg_strdup(popt->title); /* * footers and translate_columns are never set in psql's print settings, * so we needn't write code to duplicate them. */ Assert(popt->footers == NULL); Assert(popt->translate_columns == NULL); return save; } /* * restorePsetInfo: restore *popt from the previously-saved copy *save, * then free *save. */ void restorePsetInfo(printQueryOpt *popt, printQueryOpt *save) { /* Free all the old data we're about to overwrite the pointers to. */ /* topt.line_style points to const data that need not be duplicated */ free(popt->topt.fieldSep.separator); free(popt->topt.recordSep.separator); free(popt->topt.tableAttr); free(popt->nullPrint); free(popt->title); /* * footers and translate_columns are never set in psql's print settings, * so we needn't write code to duplicate them. */ Assert(popt->footers == NULL); Assert(popt->translate_columns == NULL); /* Now we may flat-copy all the fields, including pointers. */ memcpy(popt, save, sizeof(printQueryOpt)); /* Lastly, free "save" ... but its sub-structures now belong to popt. */ free(save); } static const char * pset_bool_string(bool val) { return val ? "on" : "off"; } static char * pset_quoted_string(const char *str) { char *ret = pg_malloc(strlen(str) * 2 + 3); char *r = ret; *r++ = '\''; for (; *str; str++) { if (*str == '\n') { *r++ = '\\'; *r++ = 'n'; } else if (*str == '\'') { *r++ = '\\'; *r++ = '\''; } else *r++ = *str; } *r++ = '\''; *r = '\0'; return ret; } /* * Return a malloc'ed string for the \pset value. * * Note that for some string parameters, print.c distinguishes between unset * and empty string, but for others it doesn't. This function should produce * output that produces the correct setting when fed back into \pset. */ static char * pset_value_string(const char *param, printQueryOpt *popt) { Assert(param != NULL); if (strcmp(param, "border") == 0) return psprintf("%d", popt->topt.border); else if (strcmp(param, "columns") == 0) return psprintf("%d", popt->topt.columns); else if (strcmp(param, "csv_fieldsep") == 0) return pset_quoted_string(popt->topt.csvFieldSep); else if (strcmp(param, "expanded") == 0) return pstrdup(popt->topt.expanded == 2 ? "auto" : pset_bool_string(popt->topt.expanded)); else if (strcmp(param, "fieldsep") == 0) return pset_quoted_string(popt->topt.fieldSep.separator ? popt->topt.fieldSep.separator : ""); else if (strcmp(param, "fieldsep_zero") == 0) return pstrdup(pset_bool_string(popt->topt.fieldSep.separator_zero)); else if (strcmp(param, "footer") == 0) return pstrdup(pset_bool_string(popt->topt.default_footer)); else if (strcmp(param, "format") == 0) return pstrdup(_align2string(popt->topt.format)); else if (strcmp(param, "linestyle") == 0) return pstrdup(get_line_style(&popt->topt)->name); else if (strcmp(param, "null") == 0) return pset_quoted_string(popt->nullPrint ? popt->nullPrint : ""); else if (strcmp(param, "numericlocale") == 0) return pstrdup(pset_bool_string(popt->topt.numericLocale)); else if (strcmp(param, "pager") == 0) return psprintf("%d", popt->topt.pager); else if (strcmp(param, "pager_min_lines") == 0) return psprintf("%d", popt->topt.pager_min_lines); else if (strcmp(param, "recordsep") == 0) return pset_quoted_string(popt->topt.recordSep.separator ? popt->topt.recordSep.separator : ""); else if (strcmp(param, "recordsep_zero") == 0) return pstrdup(pset_bool_string(popt->topt.recordSep.separator_zero)); else if (strcmp(param, "tableattr") == 0) return popt->topt.tableAttr ? pset_quoted_string(popt->topt.tableAttr) : pstrdup(""); else if (strcmp(param, "title") == 0) return popt->title ? pset_quoted_string(popt->title) : pstrdup(""); else if (strcmp(param, "tuples_only") == 0) return pstrdup(pset_bool_string(popt->topt.tuples_only)); else if (strcmp(param, "unicode_border_linestyle") == 0) return pstrdup(_unicode_linestyle2string(popt->topt.unicode_border_linestyle)); else if (strcmp(param, "unicode_column_linestyle") == 0) return pstrdup(_unicode_linestyle2string(popt->topt.unicode_column_linestyle)); else if (strcmp(param, "unicode_header_linestyle") == 0) return pstrdup(_unicode_linestyle2string(popt->topt.unicode_header_linestyle)); else if (strcmp(param, "xheader_width") == 0) { if (popt->topt.expanded_header_width_type == PRINT_XHEADER_FULL) return(pstrdup("full")); else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_COLUMN) return(pstrdup("column")); else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_PAGE) return(pstrdup("page")); else { /* must be PRINT_XHEADER_EXACT_WIDTH */ char wbuff[32]; snprintf(wbuff, sizeof(wbuff), "%d", popt->topt.expanded_header_exact_width); return pstrdup(wbuff); } } else return pstrdup("ERROR"); } #ifndef WIN32 #define DEFAULT_SHELL "/bin/sh" #else /* * CMD.EXE is in different places in different Win32 releases so we * have to rely on the path to find it. */ #define DEFAULT_SHELL "cmd.exe" #endif static bool do_shell(const char *command) { int result; fflush(NULL); if (!command) { char *sys; const char *shellName; shellName = getenv("SHELL"); #ifdef WIN32 if (shellName == NULL) shellName = getenv("COMSPEC"); #endif if (shellName == NULL) shellName = DEFAULT_SHELL; /* See EDITOR handling comment for an explanation */ #ifndef WIN32 sys = psprintf("exec %s", shellName); #else sys = psprintf("\"%s\"", shellName); #endif result = system(sys); free(sys); } else result = system(command); SetShellResultVariables(result); if (result == 127 || result == -1) { pg_log_error("\\!: failed"); return false; } return true; } /* * do_watch -- handler for \watch * * We break this out of exec_command to avoid having to plaster "volatile" * onto a bunch of exec_command's variables to silence stupider compilers. */ static bool do_watch(PQExpBuffer query_buf, double sleep, int iter) { long sleep_ms = (long) (sleep * 1000); printQueryOpt myopt = pset.popt; const char *strftime_fmt; const char *user_title; char *title; const char *pagerprog = NULL; FILE *pagerpipe = NULL; int title_len; int res = 0; #ifndef WIN32 sigset_t sigalrm_sigchld_sigint; sigset_t sigalrm_sigchld; sigset_t sigint; struct itimerval interval; bool done = false; #endif if (!query_buf || query_buf->len <= 0) { pg_log_error("\\watch cannot be used with an empty query"); return false; } #ifndef WIN32 sigemptyset(&sigalrm_sigchld_sigint); sigaddset(&sigalrm_sigchld_sigint, SIGCHLD); sigaddset(&sigalrm_sigchld_sigint, SIGALRM); sigaddset(&sigalrm_sigchld_sigint, SIGINT); sigemptyset(&sigalrm_sigchld); sigaddset(&sigalrm_sigchld, SIGCHLD); sigaddset(&sigalrm_sigchld, SIGALRM); sigemptyset(&sigint); sigaddset(&sigint, SIGINT); /* * Block SIGALRM and SIGCHLD before we start the timer and the pager (if * configured), to avoid races. sigwait() will receive them. */ sigprocmask(SIG_BLOCK, &sigalrm_sigchld, NULL); /* * Set a timer to interrupt sigwait() so we can run the query at the * requested intervals. */ interval.it_value.tv_sec = sleep_ms / 1000; interval.it_value.tv_usec = (sleep_ms % 1000) * 1000; interval.it_interval = interval.it_value; if (setitimer(ITIMER_REAL, &interval, NULL) < 0) { pg_log_error("could not set timer: %m"); done = true; } #endif /* * For \watch, we ignore the size of the result and always use the pager * if PSQL_WATCH_PAGER is set. We also ignore the regular PSQL_PAGER or * PAGER environment variables, because traditional pagers probably won't * be very useful for showing a stream of results. */ #ifndef WIN32 pagerprog = getenv("PSQL_WATCH_PAGER"); #endif if (pagerprog && myopt.topt.pager) { fflush(NULL); disable_sigpipe_trap(); pagerpipe = popen(pagerprog, "w"); if (!pagerpipe) /* silently proceed without pager */ restore_sigpipe_trap(); } /* * Choose format for timestamps. We might eventually make this a \pset * option. In the meantime, using a variable for the format suppresses * overly-anal-retentive gcc warnings about %c being Y2K sensitive. */ strftime_fmt = "%c"; /* * Set up rendering options, in particular, disable the pager unless * PSQL_WATCH_PAGER was successfully launched. */ if (!pagerpipe) myopt.topt.pager = 0; /* * If there's a title in the user configuration, make sure we have room * for it in the title buffer. Allow 128 bytes for the timestamp plus 128 * bytes for the rest. */ user_title = myopt.title; title_len = (user_title ? strlen(user_title) : 0) + 256; title = pg_malloc(title_len); for (;;) { time_t timer; char timebuf[128]; /* * Prepare title for output. Note that we intentionally include a * newline at the end of the title; this is somewhat historical but it * makes for reasonably nicely formatted output in simple cases. */ timer = time(NULL); strftime(timebuf, sizeof(timebuf), strftime_fmt, localtime(&timer)); if (user_title) snprintf(title, title_len, _("%s\t%s (every %gs)\n"), user_title, timebuf, sleep); else snprintf(title, title_len, _("%s (every %gs)\n"), timebuf, sleep); myopt.title = title; /* Run the query and print out the result */ res = PSQLexecWatch(query_buf->data, &myopt, pagerpipe); /* * PSQLexecWatch handles the case where we can no longer repeat the * query, and returns 0 or -1. */ if (res <= 0) break; /* If we have iteration count, check that it's not exceeded yet */ if (iter && (--iter <= 0)) break; if (pagerpipe && ferror(pagerpipe)) break; if (sleep == 0) continue; #ifdef WIN32 /* * Set up cancellation of 'watch' via SIGINT. We redo this each time * through the loop since it's conceivable something inside * PSQLexecWatch could change sigint_interrupt_jmp. */ if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) break; /* * Enable 'watch' cancellations and wait a while before running the * query again. Break the sleep into short intervals (at most 1s). */ sigint_interrupt_enabled = true; for (long i = sleep_ms; i > 0;) { long s = Min(i, 1000L); pg_usleep(s * 1000L); if (cancel_pressed) break; i -= s; } sigint_interrupt_enabled = false; #else /* sigwait() will handle SIGINT. */ sigprocmask(SIG_BLOCK, &sigint, NULL); if (cancel_pressed) done = true; /* Wait for SIGINT, SIGCHLD or SIGALRM. */ while (!done) { int signal_received; errno = sigwait(&sigalrm_sigchld_sigint, &signal_received); if (errno != 0) { /* Some other signal arrived? */ if (errno == EINTR) continue; else { pg_log_error("could not wait for signals: %m"); done = true; break; } } /* On ^C or pager exit, it's time to stop running the query. */ if (signal_received == SIGINT || signal_received == SIGCHLD) done = true; /* Otherwise, we must have SIGALRM. Time to run the query again. */ break; } /* Unblock SIGINT so that slow queries can be interrupted. */ sigprocmask(SIG_UNBLOCK, &sigint, NULL); if (done) break; #endif } if (pagerpipe) { pclose(pagerpipe); restore_sigpipe_trap(); } else { /* * If the terminal driver echoed "^C", libedit/libreadline might be * confused about the cursor position. Therefore, inject a newline * before the next prompt is displayed. We only do this when not * using a pager, because pagers are expected to restore the screen to * a sane state on exit. */ fprintf(stdout, "\n"); fflush(stdout); } #ifndef WIN32 /* Disable the interval timer. */ memset(&interval, 0, sizeof(interval)); setitimer(ITIMER_REAL, &interval, NULL); /* Unblock SIGINT, SIGCHLD and SIGALRM. */ sigprocmask(SIG_UNBLOCK, &sigalrm_sigchld_sigint, NULL); #endif pg_free(title); return (res >= 0); } /* * a little code borrowed from PSQLexec() to manage ECHO_HIDDEN output. * returns true unless we have ECHO_HIDDEN_NOEXEC. */ static bool echo_hidden_command(const char *query) { if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF) { printf(_("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(stdout); if (pset.logfile) { fprintf(pset.logfile, _("********* QUERY **********\n" "%s\n" "**************************\n\n"), query); fflush(pset.logfile); } if (pset.echo_hidden == PSQL_ECHO_HIDDEN_NOEXEC) return false; } return true; } /* * Look up the object identified by obj_type and desc. If successful, * store its OID in *obj_oid and return true, else return false. * * Note that we'll fail if the object doesn't exist OR if there are multiple * matching candidates OR if there's something syntactically wrong with the * object description; unfortunately it can be hard to tell the difference. */ static bool lookup_object_oid(EditableObjectType obj_type, const char *desc, Oid *obj_oid) { bool result = true; PQExpBuffer query = createPQExpBuffer(); PGresult *res; switch (obj_type) { case EditableFunction: /* * We have a function description, e.g. "x" or "x(int)". Issue a * query to retrieve the function's OID using a cast to regproc or * regprocedure (as appropriate). */ appendPQExpBufferStr(query, "SELECT "); appendStringLiteralConn(query, desc, pset.db); appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid", strchr(desc, '(') ? "regprocedure" : "regproc"); break; case EditableView: /* * Convert view name (possibly schema-qualified) to OID. Note: * this code doesn't check if the relation is actually a view. * We'll detect that in get_create_object_cmd(). */ appendPQExpBufferStr(query, "SELECT "); appendStringLiteralConn(query, desc, pset.db); appendPQExpBufferStr(query, "::pg_catalog.regclass::pg_catalog.oid"); break; } if (!echo_hidden_command(query->data)) { destroyPQExpBuffer(query); return false; } res = PQexec(pset.db, query->data); if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) *obj_oid = atooid(PQgetvalue(res, 0, 0)); else { minimal_error_message(res); result = false; } PQclear(res); destroyPQExpBuffer(query); return result; } /* * Construct a "CREATE OR REPLACE ..." command that describes the specified * database object. If successful, the result is stored in buf. */ static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid, PQExpBuffer buf) { bool result = true; PQExpBuffer query = createPQExpBuffer(); PGresult *res; switch (obj_type) { case EditableFunction: printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid); break; case EditableView: /* * pg_get_viewdef() just prints the query, so we must prepend * CREATE for ourselves. We must fully qualify the view name to * ensure the right view gets replaced. Also, check relation kind * to be sure it's a view. * * Starting with PG 9.4, views may have WITH [LOCAL|CASCADED] * CHECK OPTION. These are not part of the view definition * returned by pg_get_viewdef() and so need to be retrieved * separately. Materialized views (introduced in 9.3) may have * arbitrary storage parameter reloptions. */ if (pset.sversion >= 90400) { printfPQExpBuffer(query, "SELECT nspname, relname, relkind, " "pg_catalog.pg_get_viewdef(c.oid, true), " "pg_catalog.array_remove(pg_catalog.array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption " "FROM pg_catalog.pg_class c " "LEFT JOIN pg_catalog.pg_namespace n " "ON c.relnamespace = n.oid WHERE c.oid = %u", oid); } else { printfPQExpBuffer(query, "SELECT nspname, relname, relkind, " "pg_catalog.pg_get_viewdef(c.oid, true), " "c.reloptions AS reloptions, " "NULL AS checkoption " "FROM pg_catalog.pg_class c " "LEFT JOIN pg_catalog.pg_namespace n " "ON c.relnamespace = n.oid WHERE c.oid = %u", oid); } break; } if (!echo_hidden_command(query->data)) { destroyPQExpBuffer(query); return false; } res = PQexec(pset.db, query->data); if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) { resetPQExpBuffer(buf); switch (obj_type) { case EditableFunction: appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0)); break; case EditableView: { char *nspname = PQgetvalue(res, 0, 0); char *relname = PQgetvalue(res, 0, 1); char *relkind = PQgetvalue(res, 0, 2); char *viewdef = PQgetvalue(res, 0, 3); char *reloptions = PQgetvalue(res, 0, 4); char *checkoption = PQgetvalue(res, 0, 5); /* * If the backend ever supports CREATE OR REPLACE * MATERIALIZED VIEW, allow that here; but as of today it * does not, so editing a matview definition in this way * is impossible. */ switch (relkind[0]) { #ifdef NOT_USED case RELKIND_MATVIEW: appendPQExpBufferStr(buf, "CREATE OR REPLACE MATERIALIZED VIEW "); break; #endif case RELKIND_VIEW: appendPQExpBufferStr(buf, "CREATE OR REPLACE VIEW "); break; default: pg_log_error("\"%s.%s\" is not a view", nspname, relname); result = false; break; } appendPQExpBuffer(buf, "%s.", fmtId(nspname)); appendPQExpBufferStr(buf, fmtId(relname)); /* reloptions, if not an empty array "{}" */ if (reloptions != NULL && strlen(reloptions) > 2) { appendPQExpBufferStr(buf, "\n WITH ("); if (!appendReloptionsArray(buf, reloptions, "", pset.encoding, standard_strings())) { pg_log_error("could not parse reloptions array"); result = false; } appendPQExpBufferChar(buf, ')'); } /* View definition from pg_get_viewdef (a SELECT query) */ appendPQExpBuffer(buf, " AS\n%s", viewdef); /* Get rid of the semicolon that pg_get_viewdef appends */ if (buf->len > 0 && buf->data[buf->len - 1] == ';') buf->data[--(buf->len)] = '\0'; /* WITH [LOCAL|CASCADED] CHECK OPTION */ if (checkoption && checkoption[0] != '\0') appendPQExpBuffer(buf, "\n WITH %s CHECK OPTION", checkoption); } break; } /* Make sure result ends with a newline */ if (buf->len > 0 && buf->data[buf->len - 1] != '\n') appendPQExpBufferChar(buf, '\n'); } else { minimal_error_message(res); result = false; } PQclear(res); destroyPQExpBuffer(query); return result; } /* * If the given argument of \ef or \ev ends with a line number, delete the line * number from the argument string and return it as an integer. (We need * this kluge because we're too lazy to parse \ef's function or \ev's view * argument carefully --- we just slop it up in OT_WHOLE_LINE mode.) * * Returns -1 if no line number is present, 0 on error, or a positive value * on success. */ static int strip_lineno_from_objdesc(char *obj) { char *c; int lineno; if (!obj || obj[0] == '\0') return -1; c = obj + strlen(obj) - 1; /* * This business of parsing backwards is dangerous as can be in a * multibyte environment: there is no reason to believe that we are * looking at the first byte of a character, nor are we necessarily * working in a "safe" encoding. Fortunately the bitpatterns we are * looking for are unlikely to occur as non-first bytes, but beware of * trying to expand the set of cases that can be recognized. We must * guard the macros by using isascii() first, too. */ /* skip trailing whitespace */ while (c > obj && isascii((unsigned char) *c) && isspace((unsigned char) *c)) c--; /* must have a digit as last non-space char */ if (c == obj || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c)) return -1; /* find start of digit string */ while (c > obj && isascii((unsigned char) *c) && isdigit((unsigned char) *c)) c--; /* digits must be separated from object name by space or closing paren */ /* notice also that we are not allowing an empty object name ... */ if (c == obj || !isascii((unsigned char) *c) || !(isspace((unsigned char) *c) || *c == ')')) return -1; /* parse digit string */ c++; lineno = atoi(c); if (lineno < 1) { pg_log_error("invalid line number: %s", c); return 0; } /* strip digit string from object name */ *c = '\0'; return lineno; } /* * Count number of lines in the buffer. * This is used to test if pager is needed or not. */ static int count_lines_in_buf(PQExpBuffer buf) { int lineno = 0; const char *lines = buf->data; while (*lines != '\0') { lineno++; /* find start of next line */ lines = strchr(lines, '\n'); if (!lines) break; lines++; } return lineno; } /* * Write text at *lines to output with line numbers. * * For functions, lineno "1" should correspond to the first line of the * function body; lines before that are unnumbered. We expect that * pg_get_functiondef() will emit that on a line beginning with "AS ", * "BEGIN ", or "RETURN ", and that there can be no such line before * the real start of the function body. * * Caution: this scribbles on *lines. */ static void print_with_linenumbers(FILE *output, char *lines, bool is_func) { bool in_header = is_func; int lineno = 0; while (*lines != '\0') { char *eol; if (in_header && (strncmp(lines, "AS ", 3) == 0 || strncmp(lines, "BEGIN ", 6) == 0 || strncmp(lines, "RETURN ", 7) == 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; } } /* * Report just the primary error; this is to avoid cluttering the output * with, for instance, a redisplay of the internally generated query */ static void minimal_error_message(PGresult *res) { PQExpBuffer msg; const char *fld; msg = createPQExpBuffer(); fld = PQresultErrorField(res, PG_DIAG_SEVERITY); if (fld) printfPQExpBuffer(msg, "%s: ", fld); else printfPQExpBuffer(msg, "ERROR: "); fld = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); if (fld) appendPQExpBufferStr(msg, fld); else appendPQExpBufferStr(msg, "(not available)"); appendPQExpBufferChar(msg, '\n'); pg_log_error("%s", msg->data); destroyPQExpBuffer(msg); }