#include "postgres_fe.h"
#include "copy.h"
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "dumputils.h"
#include "settings.h"
#include "common.h"
#include "prompt.h"
#include "stringutils.h"
Go to the source code of this file.
Data Structures | |
struct | copy_options |
Defines | |
#define | COPYBUFSIZ 8192 |
Functions | |
static void | free_copy_options (struct copy_options *ptr) |
static void | xstrcat (char **var, const char *more) |
static struct copy_options * | parse_slash_copy (const char *args) |
bool | do_copy (const char *args) |
bool | handleCopyOut (PGconn *conn, FILE *copystream) |
bool | handleCopyIn (PGconn *conn, FILE *copystream, bool isbinary) |
#define COPYBUFSIZ 8192 |
Definition at line 523 of file copy.c.
Referenced by handleCopyIn().
bool do_copy | ( | const char * | args | ) |
Definition at line 270 of file copy.c.
References appendPQExpBuffer(), appendPQExpBufferStr(), canonicalize_path(), _psqlSettings::cur_cmd_source, PQExpBufferData::data, free, free_copy_options(), initPQExpBuffer(), NULL, parse_slash_copy(), PG_BINARY_R, PG_BINARY_W, pqsignal(), printfPQExpBuffer(), pset, psql_error(), _psqlSettings::queryFout, SendQuery(), SIG_DFL, SIG_IGN, SIGPIPE, strerror(), termPQExpBuffer(), and wait_result_to_str().
Referenced by exec_command().
{ PQExpBufferData query; FILE *copystream; FILE *save_file; FILE **override_file; struct copy_options *options; bool success; struct stat st; /* parse options */ options = parse_slash_copy(args); if (!options) return false; /* prepare to read or write the target file */ if (options->file && !options->program) canonicalize_path(options->file); if (options->from) { override_file = &pset.cur_cmd_source; if (options->file) { if (options->program) { fflush(stdout); fflush(stderr); errno = 0; copystream = popen(options->file, PG_BINARY_R); } else copystream = fopen(options->file, PG_BINARY_R); } else if (!options->psql_inout) copystream = pset.cur_cmd_source; else copystream = stdin; } else { override_file = &pset.queryFout; if (options->file) { if (options->program) { fflush(stdout); fflush(stderr); errno = 0; #ifndef WIN32 pqsignal(SIGPIPE, SIG_IGN); #endif copystream = popen(options->file, PG_BINARY_W); } else copystream = fopen(options->file, PG_BINARY_W); } else if (!options->psql_inout) copystream = pset.queryFout; else copystream = stdout; } if (!copystream) { if (options->program) psql_error("could not execute command \"%s\": %s\n", options->file, strerror(errno)); else psql_error("%s: %s\n", options->file, strerror(errno)); free_copy_options(options); return false; } if (!options->program) { /* make sure the specified file is not a directory */ fstat(fileno(copystream), &st); if (S_ISDIR(st.st_mode)) { fclose(copystream); psql_error("%s: cannot copy from/to a directory\n", options->file); free_copy_options(options); return false; } } /* build the command we will send to the backend */ initPQExpBuffer(&query); printfPQExpBuffer(&query, "COPY "); appendPQExpBufferStr(&query, options->before_tofrom); if (options->from) appendPQExpBuffer(&query, " FROM STDIN "); else appendPQExpBuffer(&query, " TO STDOUT "); if (options->after_tofrom) appendPQExpBufferStr(&query, options->after_tofrom); /* Run it like a user command, interposing the data source or sink. */ save_file = *override_file; *override_file = copystream; success = SendQuery(query.data); *override_file = save_file; termPQExpBuffer(&query); if (options->file != NULL) { if (options->program) { int pclose_rc = pclose(copystream); if (pclose_rc != 0) { if (pclose_rc < 0) psql_error("could not close pipe to external command: %s\n", strerror(errno)); else { char *reason = wait_result_to_str(pclose_rc); psql_error("%s: %s\n", options->file, reason ? reason : ""); if (reason) free(reason); } success = false; } #ifndef WIN32 pqsignal(SIGPIPE, SIG_DFL); #endif } else { if (fclose(copystream) != 0) { psql_error("%s: %s\n", options->file, strerror(errno)); success = false; } } } free_copy_options(options); return success; }
static void free_copy_options | ( | struct copy_options * | ptr | ) | [static] |
Definition at line 65 of file copy.c.
References copy_options::after_tofrom, copy_options::before_tofrom, copy_options::file, and free.
Referenced by do_copy(), and parse_slash_copy().
{ if (!ptr) return; free(ptr->before_tofrom); free(ptr->after_tofrom); free(ptr->file); free(ptr); }
Definition at line 526 of file copy.c.
References _, buf, COPYBUFSIZ, _psqlSettings::cur_cmd_source, _psqlSettings::db, get_prompt(), _psqlSettings::lineno, NULL, PGRES_COMMAND_OK, PGRES_COPY_IN, PQclear(), PQerrorMessage(), PQgetResult(), PQputCopyData(), PQputCopyEnd(), PQresultStatus(), PROMPT_COPY, pset, psql_error(), _psqlSettings::quiet, sigint_interrupt_enabled, sigint_interrupt_jmp, and sigsetjmp.
Referenced by ProcessResult().
{ bool OK; const char *prompt; char buf[COPYBUFSIZ]; PGresult *res; /* * Establish longjmp destination for exiting from wait-for-input. (This is * only effective while sigint_interrupt_enabled is TRUE.) */ if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) { /* got here with longjmp */ /* Terminate data transfer */ PQputCopyEnd(conn, _("canceled by user")); OK = false; goto copyin_cleanup; } /* Prompt if interactive input */ if (isatty(fileno(copystream))) { if (!pset.quiet) puts(_("Enter data to be copied followed by a newline.\n" "End with a backslash and a period on a line by itself.")); prompt = get_prompt(PROMPT_COPY); } else prompt = NULL; OK = true; if (isbinary) { /* interactive input probably silly, but give one prompt anyway */ if (prompt) { fputs(prompt, stdout); fflush(stdout); } for (;;) { int buflen; /* enable longjmp while waiting for input */ sigint_interrupt_enabled = true; buflen = fread(buf, 1, COPYBUFSIZ, copystream); sigint_interrupt_enabled = false; if (buflen <= 0) break; if (PQputCopyData(conn, buf, buflen) <= 0) { OK = false; break; } } } else { bool copydone = false; while (!copydone) { /* for each input line ... */ bool firstload; bool linedone; if (prompt) { fputs(prompt, stdout); fflush(stdout); } firstload = true; linedone = false; while (!linedone) { /* for each bufferload in line ... */ int linelen; char *fgresult; /* enable longjmp while waiting for input */ sigint_interrupt_enabled = true; fgresult = fgets(buf, sizeof(buf), copystream); sigint_interrupt_enabled = false; if (!fgresult) { copydone = true; break; } linelen = strlen(buf); /* current line is done? */ if (linelen > 0 && buf[linelen - 1] == '\n') linedone = true; /* check for EOF marker, but not on a partial line */ if (firstload) { if (strcmp(buf, "\\.\n") == 0 || strcmp(buf, "\\.\r\n") == 0) { copydone = true; break; } firstload = false; } if (PQputCopyData(conn, buf, linelen) <= 0) { OK = false; copydone = true; break; } } if (copystream == pset.cur_cmd_source) pset.lineno++; } } /* Check for read error */ if (ferror(copystream)) OK = false; /* Terminate data transfer */ if (PQputCopyEnd(conn, OK ? NULL : _("aborted because of read failure")) <= 0) OK = false; copyin_cleanup: /* * Check command status and return to normal libpq state * * We must not ever return with the status still PGRES_COPY_IN. Our * caller is unable to distinguish that situation from reaching the next * COPY in a command string that happened to contain two consecutive COPY * FROM STDIN commands. XXX if something makes PQputCopyEnd() fail * indefinitely while retaining status PGRES_COPY_IN, we get an infinite * loop. This is more realistic than handleCopyOut()'s counterpart risk. */ while (res = PQgetResult(conn), PQresultStatus(res) == PGRES_COPY_IN) { OK = false; PQclear(res); PQputCopyEnd(pset.db, _("trying to exit copy mode")); } if (PQresultStatus(res) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(conn)); OK = false; } PQclear(res); return OK; }
Definition at line 436 of file copy.c.
References buf, PGRES_COMMAND_OK, PGRES_COPY_OUT, PQclear(), PQerrorMessage(), PQexec(), PQfreemem(), PQgetCopyData(), PQgetResult(), PQresultStatus(), psql_error(), and strerror().
Referenced by ProcessResult().
{ bool OK = true; char *buf; int ret; PGresult *res; for (;;) { ret = PQgetCopyData(conn, &buf, 0); if (ret < 0) break; /* done or error */ if (buf) { if (fwrite(buf, 1, ret, copystream) != ret) { if (OK) /* complain only once, keep reading data */ psql_error("could not write COPY data: %s\n", strerror(errno)); OK = false; } PQfreemem(buf); } } if (OK && fflush(copystream)) { psql_error("could not write COPY data: %s\n", strerror(errno)); OK = false; } if (ret == -2) { psql_error("COPY data transfer failed: %s", PQerrorMessage(conn)); OK = false; } /* * Check command status and return to normal libpq state. After a * client-side error, the server will remain ready to deliver data. The * cleanest thing is to fully drain and discard that data. If the * client-side error happened early in a large file, this takes a long * time. Instead, take advantage of the fact that PQexec() will silently * end any ongoing PGRES_COPY_OUT state. This does cause us to lose the * results of any commands following the COPY in a single command string. * It also only works for protocol version 3. XXX should we clean up * using the slow way when the connection is using protocol version 2? * * We must not ever return with the status still PGRES_COPY_OUT. Our * caller is unable to distinguish that situation from reaching the next * COPY in a command string that happened to contain two consecutive COPY * TO STDOUT commands. We trust that no condition can make PQexec() fail * indefinitely while retaining status PGRES_COPY_OUT. */ while (res = PQgetResult(conn), PQresultStatus(res) == PGRES_COPY_OUT) { OK = false; PQclear(res); PQexec(conn, "-- clear PGRES_COPY_OUT state"); } if (PQresultStatus(res) != PGRES_COMMAND_OK) { psql_error("%s", PQerrorMessage(conn)); OK = false; } PQclear(res); return OK; }
static struct copy_options* parse_slash_copy | ( | const char * | args | ) | [static, read] |
Definition at line 91 of file copy.c.
References copy_options::after_tofrom, copy_options::before_tofrom, _psqlSettings::encoding, error(), expand_tilde(), copy_options::file, free_copy_options(), copy_options::from, NULL, pg_malloc0(), pg_strcasecmp(), pg_strdup(), copy_options::program, pset, psql_error(), copy_options::psql_inout, standard_strings(), strip_quotes(), strtokx(), and xstrcat().
Referenced by do_copy().
{ struct copy_options *result; char *token; const char *whitespace = " \t\n\r"; char nonstd_backslash = standard_strings() ? 0 : '\\'; if (!args) { psql_error("\\copy: arguments required\n"); return NULL; } result = pg_malloc0(sizeof(struct copy_options)); result->before_tofrom = pg_strdup(""); /* initialize for appending */ token = strtokx(args, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; /* The following can be removed when we drop 7.3 syntax support */ if (pg_strcasecmp(token, "binary") == 0) { xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; } /* Handle COPY (SELECT) case */ if (token[0] == '(') { int parens = 1; while (parens > 0) { xstrcat(&result->before_tofrom, " "); xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, "()", "\"'", nonstd_backslash, true, false, pset.encoding); if (!token) goto error; if (token[0] == '(') parens++; else if (token[0] == ')') parens--; } } xstrcat(&result->before_tofrom, " "); xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; /* * strtokx() will not have returned a multi-character token starting with * '.', so we don't need strcmp() here. Likewise for '(', etc, below. */ if (token[0] == '.') { /* handle schema . table */ xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; } if (token[0] == '(') { /* handle parenthesized column list */ for (;;) { xstrcat(&result->before_tofrom, " "); xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, "()", "\"", 0, false, false, pset.encoding); if (!token) goto error; if (token[0] == ')') break; } xstrcat(&result->before_tofrom, " "); xstrcat(&result->before_tofrom, token); token = strtokx(NULL, whitespace, ".,()", "\"", 0, false, false, pset.encoding); if (!token) goto error; } if (pg_strcasecmp(token, "from") == 0) result->from = true; else if (pg_strcasecmp(token, "to") == 0) result->from = false; else goto error; /* { 'filename' | PROGRAM 'command' | STDIN | STDOUT | PSTDIN | PSTDOUT } */ token = strtokx(NULL, whitespace, NULL, "'", 0, false, false, pset.encoding); if (!token) goto error; if (pg_strcasecmp(token, "program") == 0) { int toklen; token = strtokx(NULL, whitespace, NULL, "'", 0, false, false, pset.encoding); if (!token) goto error; /* * The shell command must be quoted. This isn't fool-proof, but catches * most quoting errors. */ toklen = strlen(token); if (token[0] != '\'' || toklen < 2 || token[toklen - 1] != '\'') goto error; strip_quotes(token, '\'', 0, pset.encoding); result->program = true; result->file = pg_strdup(token); } else if (pg_strcasecmp(token, "stdin") == 0 || pg_strcasecmp(token, "stdout") == 0) { result->file = NULL; } else if (pg_strcasecmp(token, "pstdin") == 0 || pg_strcasecmp(token, "pstdout") == 0) { result->psql_inout = true; result->file = NULL; } else { /* filename can be optionally quoted */ strip_quotes(token, '\'', 0, pset.encoding); result->file = pg_strdup(token); expand_tilde(&result->file); } /* Collect the rest of the line (COPY options) */ token = strtokx(NULL, "", NULL, NULL, 0, false, false, pset.encoding); if (token) result->after_tofrom = pg_strdup(token); return result; error: if (token) psql_error("\\copy: parse error at \"%s\"\n", token); else psql_error("\\copy: parse error at end of line\n"); free_copy_options(result); return NULL; }
static void xstrcat | ( | char ** | var, | |
const char * | more | |||
) | [static] |
Definition at line 78 of file copy.c.
References free, and pg_malloc().
Referenced by parse_slash_copy().