/* * psql - the PostgreSQL interactive terminal * * Copyright (c) 2000-2003, PostgreSQL Global Development Group * * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.108 2003/12/01 22:21:54 momjian Exp $ */ #include "postgres_fe.h" #include "command.h" #include #include #ifdef HAVE_PWD_H #include #endif #ifndef WIN32 #include /* for umask() */ #include /* for stat() */ #include /* open() flags */ #include /* for geteuid(), getpid(), stat() */ #else #include #include #include #include #endif #include "libpq-fe.h" #include "pqexpbuffer.h" #include "common.h" #include "copy.h" #include "describe.h" #include "help.h" #include "input.h" #include "large_obj.h" #include "mainloop.h" #include "print.h" #include "settings.h" #include "variables.h" #include "mb/pg_wchar.h" /* functions for use in this file */ static backslashResult exec_command(const char *cmd, const char *options_string, const char **continue_parse, PQExpBuffer query_buf, volatile int *paren_level); /* different ways for scan_option to handle parameter words */ enum option_type { OT_NORMAL, /* normal case */ OT_SQLID, /* treat as SQL identifier */ OT_SQLIDHACK, /* SQL identifier, but don't downcase */ OT_FILEPIPE /* it's a filename or pipe */ }; static char *scan_option(char **string, enum option_type type, char *quote, bool semicolon); static char *unescape(const unsigned char *source, size_t len); static bool do_edit(const char *filename_arg, PQExpBuffer query_buf); static bool do_connect(const char *new_dbname, const char *new_user); static bool do_shell(const char *command); /*---------- * HandleSlashCmds: * * Handles all the different commands that start with '\', * ordinarily called by MainLoop(). * * 'line' is the current input line, which should not start with a '\' * but with the actual command name * (that is taken care of by MainLoop) * * 'query_buf' contains the query-so-far, which may be modified by * execution of the backslash command (for example, \r clears it) * query_buf can be NULL if there is no query so far. * * Returns a status code indicating what action is desired, see command.h. *---------- */ backslashResult HandleSlashCmds(const char *line, PQExpBuffer query_buf, const char **end_of_cmd, volatile int *paren_level) { backslashResult status = CMD_SKIP_LINE; char *my_line; char *options_string = NULL; size_t blank_loc; const char *continue_parse = NULL; /* tell the mainloop where the * backslash command ended */ psql_assert(line); my_line = xstrdup(line); /* * Find the first whitespace. line[blank_loc] will now be the * whitespace character or the \0 at the end * * Also look for a backslash, so stuff like \p\g works. */ blank_loc = strcspn(my_line, " \t\n\r\\"); if (my_line[blank_loc] == '\\') { continue_parse = &my_line[blank_loc]; my_line[blank_loc] = '\0'; /* If it's a double backslash, we skip it. */ if (my_line[blank_loc + 1] == '\\') continue_parse += 2; } /* do we have an option string? */ else if (my_line[blank_loc] != '\0') { options_string = &my_line[blank_loc + 1]; my_line[blank_loc] = '\0'; } status = exec_command(my_line, options_string, &continue_parse, query_buf, paren_level); if (status == CMD_UNKNOWN) { /* * If the command was not recognized, try to parse it as a * one-letter command with immediately following argument (a * still-supported, but no longer encouraged, syntax). */ char new_cmd[2]; new_cmd[0] = my_line[0]; new_cmd[1] = '\0'; /* use line for options, because my_line was clobbered above */ status = exec_command(new_cmd, line + 1, &continue_parse, query_buf, paren_level); /* * continue_parse must be relative to my_line for calculation * below */ continue_parse += my_line - line; #if 0 /* turned out to be too annoying */ if (status != CMD_UNKNOWN && isalpha((unsigned char) new_cmd[0])) psql_error("Warning: This syntax is deprecated.\n"); #endif } if (status == CMD_UNKNOWN) { if (pset.cur_cmd_interactive) fprintf(stderr, gettext("Invalid command \\%s. Try \\? for help.\n"), my_line); else psql_error("invalid command \\%s\n", my_line); status = CMD_ERROR; } if (continue_parse && *continue_parse && *(continue_parse + 1) == '\\') continue_parse += 2; if (end_of_cmd) { if (continue_parse) *end_of_cmd = line + (continue_parse - my_line); else *end_of_cmd = line + strlen(line); } free(my_line); return status; } static backslashResult exec_command(const char *cmd, const char *options_string, const char **continue_parse, PQExpBuffer query_buf, volatile int *paren_level) { bool success = true; /* indicate here if the command ran ok or * failed */ bool quiet = QUIET(); backslashResult status = CMD_SKIP_LINE; char *string, *string_cpy, *val; /* * The 'string' variable will be overwritten to point to the next * token, hence we need an extra pointer so we can free this at the * end. */ if (options_string) string = string_cpy = xstrdup(options_string); else string = string_cpy = NULL; /* * \a -- toggle field alignment This makes little sense but we keep it * around. */ if (strcmp(cmd, "a") == 0) { if (pset.popt.topt.format != PRINT_ALIGNED) success = do_pset("format", "aligned", &pset.popt, quiet); else success = do_pset("format", "unaligned", &pset.popt, quiet); } /* \C -- override table title (formerly change HTML caption) */ else if (strcmp(cmd, "C") == 0) { char *opt = scan_option(&string, OT_NORMAL, NULL, true); success = do_pset("title", opt, &pset.popt, quiet); free(opt); } /*---------- * \c or \connect -- connect to new database or as different user * * \c foo bar connect to db "foo" as user "bar" * \c foo [-] connect to db "foo" as current user * \c - bar connect to current db as user "bar" * \c connect to default db as default user *---------- */ else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) { char *opt1, *opt2; char opt1q, opt2q; /* * Ideally we should treat the arguments as SQL identifiers. But * for backwards compatibility with 7.2 and older pg_dump files, * we have to take unquoted arguments verbatim (don't downcase * them). For now, double-quoted arguments may be stripped of * double quotes (as if SQL identifiers). By 7.4 or so, pg_dump * files can be expected to double-quote all mixed-case \connect * arguments, and then we can get rid of OT_SQLIDHACK. */ opt1 = scan_option(&string, OT_SQLIDHACK, &opt1q, true); opt2 = scan_option(&string, OT_SQLIDHACK, &opt2q, true); if (opt2) /* gave username */ success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || strcmp(opt1, "") == 0) ? "" : opt1, !opt2q && (strcmp(opt2, "-") == 0 || strcmp(opt2, "") == 0) ? "" : opt2); else if (opt1) /* gave database name */ success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 || strcmp(opt1, "") == 0) ? "" : opt1, ""); else /* connect to default db as default user */ success = do_connect(NULL, NULL); free(opt1); free(opt2); } /* \cd */ else if (strcmp(cmd, "cd") == 0) { char *opt = scan_option(&string, OT_NORMAL, NULL, true); char *dir; if (opt) dir = opt; else { #ifndef WIN32 struct passwd *pw; pw = getpwuid(geteuid()); if (!pw) { psql_error("could not get home directory: %s\n", strerror(errno)); exit(EXIT_FAILURE); } dir = pw->pw_dir; #else /* WIN32 */ /* * On Windows, 'cd' without arguments prints the current * directory, so if someone wants to code this here instead... */ dir = "/"; #endif /* WIN32 */ } if (chdir(dir) == -1) { psql_error("\\%s: could not change directory to \"%s\": %s\n", cmd, dir, strerror(errno)); success = false; } if (opt) free(opt); } /* \copy */ else if (strcasecmp(cmd, "copy") == 0) { success = do_copy(options_string); if (options_string) string += strlen(string); } /* \copyright */ else if (strcmp(cmd, "copyright") == 0) print_copyright(); /* \d* commands */ else if (cmd[0] == 'd') { char *pattern; bool show_verbose; /* We don't do SQLID reduction on the pattern yet */ pattern = scan_option(&string, OT_NORMAL, NULL, true); show_verbose = strchr(cmd, '+') ? true : false; switch (cmd[1]) { case '\0': case '+': if (pattern) success = describeTableDetails(pattern, show_verbose); else /* standard listing of interesting things */ success = listTables("tvs", NULL, show_verbose); break; case 'a': success = describeAggregates(pattern, show_verbose); break; case 'c': success = listConversions(pattern); break; case 'C': success = listCasts(pattern); break; case 'd': success = objectDescription(pattern); break; case 'D': success = listDomains(pattern); break; case 'f': success = describeFunctions(pattern, show_verbose); break; case 'g': success = describeGroups(pattern); break; case 'l': success = do_lo_list(); break; case 'n': success = listSchemas(pattern); break; case 'o': success = describeOperators(pattern); break; case 'p': success = permissionsList(pattern); break; case 'T': success = describeTypes(pattern, show_verbose); break; case 't': case 'v': case 'i': case 's': case 'S': success = listTables(&cmd[1], pattern, show_verbose); break; case 'u': success = describeUsers(pattern); break; default: status = CMD_UNKNOWN; } if (pattern) free(pattern); } /* * \e or \edit -- edit the current query buffer (or a file and make it * the query buffer */ else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) { char *fname; if (!query_buf) { psql_error("no query buffer\n"); status = CMD_ERROR; } else { fname = scan_option(&string, OT_NORMAL, NULL, true); status = do_edit(fname, query_buf) ? CMD_NEWEDIT : CMD_ERROR; free(fname); } } /* \echo and \qecho */ else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0) { char *value; char quoted; bool no_newline = false; bool first = true; FILE *fout; if (strcmp(cmd, "qecho") == 0) fout = pset.queryFout; else fout = stdout; while ((value = scan_option(&string, OT_NORMAL, "ed, false))) { if (!quoted && strcmp(value, "-n") == 0) no_newline = true; else { if (first) first = false; else fputc(' ', fout); fputs(value, fout); } free(value); } if (!no_newline) fputs("\n", fout); } /* \encoding -- set/show client side encoding */ else if (strcmp(cmd, "encoding") == 0) { char *encoding = scan_option(&string, OT_NORMAL, NULL, false); if (!encoding) { /* show encoding */ puts(pg_encoding_to_char(pset.encoding)); } else { /* set encoding */ if (PQsetClientEncoding(pset.db, encoding) == -1) psql_error("%s: invalid encoding name or conversion procedure not found\n", encoding); else { /* save encoding info into psql internal data */ pset.encoding = PQclientEncoding(pset.db); pset.popt.topt.encoding = pset.encoding; SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding)); } free(encoding); } } /* \f -- change field separator */ else if (strcmp(cmd, "f") == 0) { char *fname = scan_option(&string, OT_NORMAL, NULL, false); success = do_pset("fieldsep", fname, &pset.popt, quiet); free(fname); } /* \g means send query */ else if (strcmp(cmd, "g") == 0) { char *fname = scan_option(&string, OT_FILEPIPE, NULL, false); if (!fname) pset.gfname = NULL; else pset.gfname = xstrdup(fname); free(fname); status = CMD_SEND; } /* help */ else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) { helpSQL(options_string ? &options_string[strspn(options_string, " \t\n\r")] : NULL, pset.popt.topt.pager); /* set pointer to end of line */ if (string) string += strlen(string); } /* HTML mode */ else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) { if (pset.popt.topt.format != PRINT_HTML) success = do_pset("format", "html", &pset.popt, quiet); else success = do_pset("format", "aligned", &pset.popt, quiet); } /* \i is include file */ else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0) { char *fname = scan_option(&string, OT_NORMAL, NULL, true); if (!fname) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { success = (process_file(fname) == EXIT_SUCCESS); free(fname); } } /* \l is list databases */ else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0) success = listAllDbs(false); else if (strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) success = listAllDbs(true); /* * large object things */ else if (strncmp(cmd, "lo_", 3) == 0) { char *opt1, *opt2; opt1 = scan_option(&string, OT_NORMAL, NULL, true); opt2 = scan_option(&string, OT_NORMAL, NULL, true); if (strcmp(cmd + 3, "export") == 0) { if (!opt2) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_lo_export(opt1, opt2); } else if (strcmp(cmd + 3, "import") == 0) { if (!opt1) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_lo_import(opt1, opt2); } else if (strcmp(cmd + 3, "list") == 0) success = do_lo_list(); else if (strcmp(cmd + 3, "unlink") == 0) { if (!opt1) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_lo_unlink(opt1); } else status = CMD_UNKNOWN; free(opt1); free(opt2); } /* \o -- set query output */ else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) { char *fname = scan_option(&string, OT_FILEPIPE, NULL, true); success = setQFout(fname); free(fname); } /* \p prints the current query buffer */ else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) { if (query_buf && query_buf->len > 0) puts(query_buf->data); else if (!quiet) puts(gettext("Query buffer is empty.")); fflush(stdout); } /* \pset -- set printing parameters */ else if (strcmp(cmd, "pset") == 0) { char *opt0 = scan_option(&string, OT_NORMAL, NULL, false); char *opt1 = scan_option(&string, OT_NORMAL, NULL, false); if (!opt0) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_pset(opt0, opt1, &pset.popt, quiet); free(opt0); free(opt1); } /* \q or \quit */ else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) status = CMD_TERMINATE; /* reset(clear) the buffer */ else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) { resetPQExpBuffer(query_buf); if (paren_level) *paren_level = 0; if (!quiet) puts(gettext("Query buffer reset (cleared).")); } /* \s save history in a file or show it on the screen */ else if (strcmp(cmd, "s") == 0) { char *fname = scan_option(&string, OT_NORMAL, NULL, true); success = saveHistory(fname ? fname : "/dev/tty"); if (success && !quiet && fname) printf(gettext("Wrote history to file \"%s\".\n"), fname); free(fname); } /* \set -- generalized set variable/option command */ else if (strcmp(cmd, "set") == 0) { char *opt0 = scan_option(&string, 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 = NULL; char *opt; opt = scan_option(&string, OT_NORMAL, NULL, false); newval = xstrdup(opt ? opt : ""); free(opt); while ((opt = scan_option(&string, OT_NORMAL, NULL, false))) { newval = realloc(newval, strlen(newval) + strlen(opt) + 1); if (!newval) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } strcat(newval, opt); free(opt); } if (SetVariable(pset.vars, opt0, newval)) { /* Check for special variables */ if (strcmp(opt0, "VERBOSITY") == 0) SyncVerbosityVariable(); } else { psql_error("\\%s: error\n", cmd); success = false; } free(newval); } free(opt0); } /* \t -- turn off headers and row count */ else if (strcmp(cmd, "t") == 0) success = do_pset("tuples_only", NULL, &pset.popt, quiet); /* \T -- define html attributes */ else if (strcmp(cmd, "T") == 0) { char *value = scan_option(&string, OT_NORMAL, NULL, false); success = do_pset("tableattr", value, &pset.popt, quiet); free(value); } /* \timing -- toggle timing of queries */ else if (strcmp(cmd, "timing") == 0) { pset.timing = !pset.timing; if (!quiet) { if (pset.timing) puts(gettext("Timing is on.")); else puts(gettext("Timing is off.")); } } /* \unset */ else if (strcmp(cmd, "unset") == 0) { char *opt = scan_option(&string, OT_NORMAL, NULL, false); if (!opt) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else if (!SetVariable(pset.vars, opt, NULL)) { psql_error("\\%s: error\n", cmd); success = false; } free(opt); } /* \w -- write query buffer to file */ else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) { FILE *fd = NULL; bool is_pipe = false; char *fname = NULL; if (!query_buf) { psql_error("no query buffer\n"); status = CMD_ERROR; } else { fname = scan_option(&string, OT_FILEPIPE, NULL, true); if (!fname) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { if (fname[0] == '|') { is_pipe = true; fd = popen(&fname[1], "w"); } else fd = fopen(fname, "w"); if (!fd) { psql_error("%s: %s\n", fname, strerror(errno)); success = false; } } } if (fd) { int result; if (query_buf && query_buf->len > 0) fprintf(fd, "%s\n", query_buf->data); if (is_pipe) result = pclose(fd); else result = fclose(fd); if (result == EOF) { psql_error("%s: %s\n", fname, strerror(errno)); success = false; } } free(fname); } /* \x -- toggle expanded table representation */ else if (strcmp(cmd, "x") == 0) success = do_pset("expanded", NULL, &pset.popt, quiet); /* \z -- list table rights (equivalent to \dp) */ else if (strcmp(cmd, "z") == 0) { char *pattern = scan_option(&string, OT_NORMAL, NULL, true); success = permissionsList(pattern); if (pattern) free(pattern); } /* \! -- shell escape */ else if (strcmp(cmd, "!") == 0) { success = do_shell(options_string); /* wind pointer to end of line */ if (string) string += strlen(string); } /* \? -- slash command help */ else if (strcmp(cmd, "?") == 0) slashUsage(pset.popt.topt.pager); #if 0 /* * These commands don't do anything. I just use them to test the * parser. */ else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0) { int i = 0; char *value; fprintf(stderr, "+ optstr = |%s|\n", options_string); while ((value = scan_option(&string, OT_NORMAL, NULL, true))) { fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value); free(value); } } #endif else status = CMD_UNKNOWN; if (!success) status = CMD_ERROR; /* eat the rest of the options string */ while ((val = scan_option(&string, OT_NORMAL, NULL, false))) { if (status != CMD_UNKNOWN) psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, val); if (val) free(val); } if (options_string && continue_parse) *continue_parse = options_string + (string - string_cpy); free(string_cpy); return status; } /* * scan_option() * * *string points to possible option string on entry; on exit, it's updated * to point past the option string (if any). * * type tells what processing, if any, to perform on the option string; * for example, if it's a SQL identifier, we want to downcase any unquoted * letters. * * if quote is not NULL, *quote is set to 0 if no quoting was found, else * the quote symbol. * * if semicolon is true, trailing semicolon(s) that would otherwise be taken * as part of the option string will be stripped. * * Return value is NULL if no option found, else a malloc'd copy of the * processed option value. */ static char * scan_option(char **string, enum option_type type, char *quote, bool semicolon) { unsigned int pos; char *options_string; char *return_val; if (quote) *quote = 0; if (!string || !(*string)) return NULL; options_string = *string; /* skip leading whitespace */ pos = strspn(options_string, " \t\n\r"); switch (options_string[pos]) { /* * End of line: no option present */ case '\0': *string = &options_string[pos]; return NULL; /* * Next command: treat like end of line * * XXX this means we can't conveniently accept options that start * with a backslash; therefore, option processing that * encourages use of backslashes is rather broken. */ case '\\': *string = &options_string[pos]; return NULL; /* * A single quote has a psql internal meaning, such as for * delimiting file names, and it also allows for such escape * sequences as \t. */ case '\'': { unsigned int jj; unsigned short int bslash_count = 0; for (jj = pos + 1; options_string[jj]; jj += PQmblen(&options_string[jj], pset.encoding)) { if (options_string[jj] == '\'' && bslash_count % 2 == 0) break; if (options_string[jj] == '\\') bslash_count++; else bslash_count = 0; } if (options_string[jj] == 0) { psql_error("parse error at the end of line\n"); *string = &options_string[jj]; return NULL; } return_val = unescape(&options_string[pos + 1], jj - pos - 1); *string = &options_string[jj + 1]; if (quote) *quote = '\''; return return_val; } /* * Backticks are for command substitution, like in shells */ case '`': { bool error = false; FILE *fd; char *file; PQExpBufferData output; char buf[512]; size_t result, len; len = strcspn(options_string + pos + 1, "`"); if (options_string[pos + 1 + len] == 0) { psql_error("parse error at the end of line\n"); *string = &options_string[pos + 1 + len]; return NULL; } options_string[pos + 1 + len] = '\0'; file = options_string + pos + 1; fd = popen(file, "r"); if (!fd) { psql_error("%s: %s\n", file, strerror(errno)); error = true; } initPQExpBuffer(&output); if (!error) { do { result = fread(buf, 1, 512, fd); if (ferror(fd)) { psql_error("%s: %s\n", file, strerror(errno)); error = true; break; } appendBinaryPQExpBuffer(&output, buf, result); } while (!feof(fd)); appendPQExpBufferChar(&output, '\0'); } if (fd && pclose(fd) == -1) { psql_error("%s: %s\n", file, strerror(errno)); error = true; } if (!error) { if (output.data[strlen(output.data) - 1] == '\n') output.data[strlen(output.data) - 1] = '\0'; return_val = output.data; } else { return_val = xstrdup(""); termPQExpBuffer(&output); } options_string[pos + 1 + len] = '`'; *string = options_string + pos + len + 2; if (quote) *quote = '`'; return return_val; } /* * Variable substitution */ case ':': { size_t token_end; const char *value; char save_char; token_end = strcspn(&options_string[pos + 1], " \t\n\r"); save_char = options_string[pos + token_end + 1]; options_string[pos + token_end + 1] = '\0'; value = GetVariable(pset.vars, options_string + pos + 1); return_val = xstrdup(value ? value : ""); options_string[pos + token_end + 1] = save_char; *string = &options_string[pos + token_end + 1]; /* XXX should we set *quote to ':' here? */ return return_val; } /* * | could be the beginning of a pipe if so, take rest of line * as command */ case '|': if (type == OT_FILEPIPE) { *string += strlen(*string); return xstrdup(options_string + pos); } /* fallthrough for other option types */ /* * Default case: token extends to next whitespace, except that * whitespace within double quotes doesn't end the token. * * If we are processing the option as a SQL identifier, then * downcase unquoted letters and remove double-quotes --- but * doubled double-quotes become output double-quotes, per * spec. * * Note that a string like FOO"BAR"BAZ will be converted to * fooBARbaz; this is somewhat inconsistent with the SQL spec, * which would have us parse it as several identifiers. But * for psql's purposes, we want a string like "foo"."bar" to * be treated as one option, so there's little choice. */ default: { bool inquotes = false; size_t token_len; char *cp; /* Find end of option */ cp = &options_string[pos]; for (;;) { /* Find next quote, whitespace, or end of string */ cp += strcspn(cp, "\" \t\n\r"); if (inquotes) { if (*cp == '\0') { psql_error("parse error at the end of line\n"); *string = cp; return NULL; } if (*cp == '"') inquotes = false; cp++; } else { if (*cp != '"') break; /* whitespace or end of string */ if (quote) *quote = '"'; inquotes = true; cp++; } } *string = cp; /* Copy the option */ token_len = cp - &options_string[pos]; return_val = malloc(token_len + 1); if (!return_val) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } memcpy(return_val, &options_string[pos], token_len); return_val[token_len] = '\0'; /* Strip any trailing semi-colons if requested */ if (semicolon) { int i; for (i = token_len - 1; i >= 0 && return_val[i] == ';'; i--) /* skip */ ; if (i < 0) { /* nothing left after stripping the semicolon... */ free(return_val); return NULL; } if (i < (int) token_len - 1) return_val[i + 1] = '\0'; } /* * If SQL identifier processing was requested, then we * strip out excess double quotes and downcase unquoted * letters. */ if (type == OT_SQLID || type == OT_SQLIDHACK) { inquotes = false; cp = return_val; while (*cp) { if (*cp == '"') { if (inquotes && cp[1] == '"') { /* Keep the first quote, remove the second */ cp++; } inquotes = !inquotes; /* Collapse out quote at *cp */ memmove(cp, cp + 1, strlen(cp)); /* do not advance cp */ } else { if (!inquotes && type == OT_SQLID) { if (isupper((unsigned char) *cp)) *cp = tolower((unsigned char) *cp); } cp += PQmblen(cp, pset.encoding); } } } return return_val; } } } /* * unescape * * Replaces \n, \t, and the like. * * The return value is malloc()'ed. */ static char * unescape(const unsigned char *source, size_t len) { const unsigned char *p; bool esc = false; /* Last character we saw was the escape * character */ char *destination, *tmp; size_t length; psql_assert(source); length = Min(len, strlen(source)) + 1; tmp = destination = malloc(length); if (!tmp) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } for (p = source; p - source < (int) len && *p; p += PQmblen(p, pset.encoding)) { if (esc) { char c; switch (*p) { case 'n': c = '\n'; break; case 't': c = '\t'; break; case 'b': c = '\b'; break; case 'r': c = '\r'; break; case 'f': c = '\f'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': c = parse_char((char **) &p); break; default: c = *p; } *tmp++ = c; esc = false; } else if (*p == '\\') esc = true; else { int i; const unsigned char *mp = p; for (i = 0; i < PQmblen(p, pset.encoding); i++) *tmp++ = *mp++; esc = false; } } *tmp = '\0'; return destination; } /* do_connect * -- handler for \connect * * Connects to a database (new_dbname) as a certain user (new_user). * The new user can be NULL. A db name of "-" is the same as the old one. * (That is, the one currently in pset. But pset.db can also be NULL. A NULL * dbname is handled by libpq.) * Returns true if all ok, false if the new connection couldn't be established. * The old connection will be kept if the session is interactive. */ static bool do_connect(const char *new_dbname, const char *new_user) { PGconn *oldconn = pset.db; const char *dbparam = NULL; const char *userparam = NULL; const char *pwparam = NULL; char *prompted_password = NULL; bool need_pass; bool success = false; /* Delete variables (in case we fail before setting them anew) */ UnsyncVariables(); /* If dbname is "" then use old name, else new one (even if NULL) */ if (oldconn && new_dbname && PQdb(oldconn) && strcmp(new_dbname, "") == 0) dbparam = PQdb(oldconn); else dbparam = new_dbname; /* If user is "" then use the old one */ if (new_user && PQuser(oldconn) && strcmp(new_user, "") == 0) userparam = PQuser(oldconn); else userparam = new_user; /* need to prompt for password? */ if (pset.getPassword) pwparam = prompted_password = simple_prompt("Password: ", 100, false); /* * Use old password (if any) if no new one given and we are * reconnecting as same user */ if (!pwparam && oldconn && PQuser(oldconn) && userparam && strcmp(PQuser(oldconn), userparam) == 0) pwparam = PQpass(oldconn); do { need_pass = false; pset.db = PQsetdbLogin(PQhost(oldconn), PQport(oldconn), NULL, NULL, dbparam, userparam, pwparam); if (PQstatus(pset.db) == CONNECTION_BAD && strcmp(PQerrorMessage(pset.db), "fe_sendauth: no password supplied\n") == 0 && !feof(stdin)) { PQfinish(pset.db); need_pass = true; free(prompted_password); prompted_password = NULL; pwparam = prompted_password = simple_prompt("Password: ", 100, false); } } while (need_pass); free(prompted_password); /* * If connection failed, try at least keep the old one. That's * probably more convenient than just kicking you out of the program. */ if (!pset.db || PQstatus(pset.db) == CONNECTION_BAD) { if (pset.cur_cmd_interactive) { psql_error("%s", PQerrorMessage(pset.db)); PQfinish(pset.db); if (oldconn) { fputs(gettext("Previous connection kept\n"), stderr); pset.db = oldconn; } else pset.db = NULL; } else { /* * we don't want unpredictable things to happen in scripting * mode */ psql_error("\\connect: %s", PQerrorMessage(pset.db)); PQfinish(pset.db); if (oldconn) PQfinish(oldconn); pset.db = NULL; } } else { if (!QUIET()) { if (userparam != new_user) /* no new user */ printf(gettext("You are now connected to database \"%s\".\n"), dbparam); else if (dbparam != new_dbname) /* no new db */ printf(gettext("You are now connected as new user \"%s\".\n"), new_user); else /* both new */ printf(gettext("You are now connected to database \"%s\" as user \"%s\".\n"), PQdb(pset.db), PQuser(pset.db)); } if (oldconn) PQfinish(oldconn); success = true; } PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL); /* Update variables */ SyncVariables(); return success; } /* * SyncVariables * * Make psql's internal variables agree with connection state upon * establishing a new connection. */ void SyncVariables(void) { /* get stuff from connection */ pset.encoding = PQclientEncoding(pset.db); pset.popt.topt.encoding = pset.encoding; SetVariable(pset.vars, "DBNAME", PQdb(pset.db)); SetVariable(pset.vars, "USER", PQuser(pset.db)); SetVariable(pset.vars, "HOST", PQhost(pset.db)); SetVariable(pset.vars, "PORT", PQport(pset.db)); SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding)); /* send stuff to it, too */ SyncVerbosityVariable(); } /* * UnsyncVariables * * Clear variables that should be not be set when there is no connection. */ void UnsyncVariables(void) { SetVariable(pset.vars, "DBNAME", NULL); SetVariable(pset.vars, "USER", NULL); SetVariable(pset.vars, "HOST", NULL); SetVariable(pset.vars, "PORT", NULL); SetVariable(pset.vars, "ENCODING", NULL); } /* * Update connection state from VERBOSITY variable */ void SyncVerbosityVariable(void) { switch (SwitchVariable(pset.vars, "VERBOSITY", "default", "terse", "verbose", NULL)) { case 1: /* default */ PQsetErrorVerbosity(pset.db, PQERRORS_DEFAULT); break; case 2: /* terse */ PQsetErrorVerbosity(pset.db, PQERRORS_TERSE); break; case 3: /* verbose */ PQsetErrorVerbosity(pset.db, PQERRORS_VERBOSE); break; default: /* not set or unrecognized value */ PQsetErrorVerbosity(pset.db, PQERRORS_DEFAULT); break; } } /* * do_edit -- handler for \e * * If you do not specify a filename, the current query buffer will be copied * into a temporary one. */ static bool editFile(const char *fname) { const char *editorName; char *sys; int result; psql_assert(fname); /* Find an editor to use */ editorName = getenv("PSQL_EDITOR"); if (!editorName) editorName = getenv("EDITOR"); if (!editorName) editorName = getenv("VISUAL"); if (!editorName) editorName = DEFAULT_EDITOR; sys = malloc(strlen(editorName) + strlen(fname) + 10 + 1); if (!sys) return false; sprintf(sys, #ifndef WIN32 "exec " #endif "%s '%s'", editorName, fname); result = system(sys); if (result == -1) psql_error("could not start editor \"%s\"\n", editorName); else if (result == 127) psql_error("could not start /bin/sh\n"); free(sys); return result == 0; } /* call this one */ static bool do_edit(const char *filename_arg, PQExpBuffer query_buf) { char fnametmp[MAXPGPATH]; FILE *stream = NULL; const char *fname; bool error = false; int fd; #ifndef WIN32 struct stat before, after; #endif if (filename_arg) fname = filename_arg; else { /* make a temp file to edit */ #ifndef WIN32 const char *tmpdirenv = getenv("TMPDIR"); snprintf(fnametmp, sizeof(fnametmp), "%s/psql.edit.%ld.%ld", tmpdirenv ? tmpdirenv : "/tmp", (long) geteuid(), (long) getpid()); #else GetTempFileName(".", "psql", 0, fnametmp); #endif fname = (const char *) fnametmp; fd = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0600); if (fd != -1) stream = fdopen(fd, "w"); if (fd == -1 || !stream) { psql_error("could not open temporary file \"%s\": %s\n", fname, strerror(errno)); error = true; } else { unsigned int ql = query_buf->len; if (ql == 0 || query_buf->data[ql - 1] != '\n') { appendPQExpBufferChar(query_buf, '\n'); ql++; } if (fwrite(query_buf->data, 1, ql, stream) != ql) { psql_error("%s: %s\n", fname, strerror(errno)); fclose(stream); remove(fname); error = true; } else fclose(stream); } } #ifndef WIN32 if (!error && stat(fname, &before) != 0) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } #endif /* call editor */ if (!error) error = !editFile(fname); #ifndef WIN32 if (!error && stat(fname, &after) != 0) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } if (!error && before.st_mtime != after.st_mtime) { #else if (!error) { #endif stream = fopen(fname, "r"); if (!stream) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } else { /* read file back in */ char line[1024]; resetPQExpBuffer(query_buf); while (fgets(line, sizeof(line), stream) != NULL) appendPQExpBufferStr(query_buf, line); if (ferror(stream)) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } #ifdef USE_READLINE #ifdef HAVE_REPLACE_HISTORY_ENTRY replace_history_entry(where_history(), query_buf->data, NULL); #else add_history(query_buf->data); #endif #endif fclose(stream); } } /* remove temp file */ if (!filename_arg) { if (remove(fname) == -1) { psql_error("%s: %s\n", fname, strerror(errno)); error = true; } } return !error; } /* * process_file * * Read commands from filename and then them to the main processing loop * Handler for \i, but can be used for other things as well. */ int process_file(char *filename) { FILE *fd; int result; char *oldfilename; if (!filename) return false; fd = fopen(filename, "r"); if (!fd) { psql_error("%s: %s\n", filename, strerror(errno)); return false; } oldfilename = pset.inputfile; pset.inputfile = filename; result = MainLoop(fd); fclose(fd); pset.inputfile = oldfilename; return result; } /* * do_pset * */ static const char * _align2string(enum printFormat in) { switch (in) { case PRINT_NOTHING: return "nothing"; break; case PRINT_UNALIGNED: return "unaligned"; break; case PRINT_ALIGNED: return "aligned"; break; case PRINT_HTML: return "html"; break; case PRINT_LATEX: return "latex"; break; } return "unknown"; } bool do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) { size_t vallen = 0; psql_assert(param); if (value) vallen = strlen(value); /* set format */ if (strcmp(param, "format") == 0) { if (!value) ; else if (strncasecmp("unaligned", value, vallen) == 0) popt->topt.format = PRINT_UNALIGNED; else if (strncasecmp("aligned", value, vallen) == 0) popt->topt.format = PRINT_ALIGNED; else if (strncasecmp("html", value, vallen) == 0) popt->topt.format = PRINT_HTML; else if (strncasecmp("latex", value, vallen) == 0) popt->topt.format = PRINT_LATEX; else { psql_error("\\pset: allowed formats are unaligned, aligned, html, latex\n"); return false; } if (!quiet) printf(gettext("Output format is %s.\n"), _align2string(popt->topt.format)); } /* set border style/width */ else if (strcmp(param, "border") == 0) { if (value) popt->topt.border = atoi(value); if (!quiet) printf(gettext("Border style is %d.\n"), popt->topt.border); } /* set expanded/vertical mode */ else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0) { popt->topt.expanded = !popt->topt.expanded; if (!quiet) printf(popt->topt.expanded ? gettext("Expanded display is on.\n") : gettext("Expanded display is off.\n")); } /* null display */ else if (strcmp(param, "null") == 0) { if (value) { free(popt->nullPrint); popt->nullPrint = xstrdup(value); } if (!quiet) printf(gettext("Null display is \"%s\".\n"), popt->nullPrint ? popt->nullPrint : ""); } /* field separator for unaligned text */ else if (strcmp(param, "fieldsep") == 0) { if (value) { free(popt->topt.fieldSep); popt->topt.fieldSep = xstrdup(value); } if (!quiet) printf(gettext("Field separator is \"%s\".\n"), popt->topt.fieldSep); } /* record separator for unaligned text */ else if (strcmp(param, "recordsep") == 0) { if (value) { free(popt->topt.recordSep); popt->topt.recordSep = xstrdup(value); } if (!quiet) { if (strcmp(popt->topt.recordSep, "\n") == 0) printf(gettext("Record separator is .")); else printf(gettext("Record separator is \"%s\".\n"), popt->topt.recordSep); } } /* toggle between full and barebones format */ else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0) { popt->topt.tuples_only = !popt->topt.tuples_only; if (!quiet) { if (popt->topt.tuples_only) puts(gettext("Showing only tuples.")); else puts(gettext("Tuples only is off.")); } } /* set title override */ else if (strcmp(param, "title") == 0) { free(popt->title); if (!value) popt->title = NULL; else popt->title = xstrdup(value); if (!quiet) { if (popt->title) printf(gettext("Title is \"%s\".\n"), popt->title); else printf(gettext("Title is unset.\n")); } } /* set HTML table tag options */ else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0) { free(popt->topt.tableAttr); if (!value) popt->topt.tableAttr = NULL; else popt->topt.tableAttr = xstrdup(value); if (!quiet) { if (popt->topt.tableAttr) printf(gettext("Table attribute is \"%s\".\n"), popt->topt.tableAttr); else printf(gettext("Table attributes unset.\n")); } } /* toggle use of pager */ else if (strcmp(param, "pager") == 0) { if (value && strcasecmp(value, "always") == 0) popt->topt.pager = 2; else if (popt->topt.pager == 1) popt->topt.pager = 0; else popt->topt.pager = 1; if (!quiet) { if (popt->topt.pager == 1) puts(gettext("Pager is used for long output.")); else if (popt->topt.pager == 2) puts(gettext("Pager is always used.")); else puts(gettext("Pager usage is off.")); } } /* disable "(x rows)" footer */ else if (strcmp(param, "footer") == 0) { popt->default_footer = !popt->default_footer; if (!quiet) { if (popt->default_footer) puts(gettext("Default footer is on.")); else puts(gettext("Default footer is off.")); } } else { psql_error("\\pset: unknown option: %s\n", param); return false; } return true; } #define DEFAULT_SHELL "/bin/sh" static bool do_shell(const char *command) { int result; if (!command) { char *sys; const char *shellName; shellName = getenv("SHELL"); if (shellName == NULL) shellName = DEFAULT_SHELL; sys = malloc(strlen(shellName) + 16); if (!sys) { psql_error("out of memory\n"); if (pset.cur_cmd_interactive) return false; else exit(EXIT_FAILURE); } sprintf(sys, #ifndef WIN32 "exec " #endif "%s", shellName); result = system(sys); free(sys); } else result = system(command); if (result == 127 || result == -1) { psql_error("\\!: failed\n"); return false; } return true; }