Header And Logo

PostgreSQL
| The world's most advanced open source database.

Data Structures | Defines | Functions

copy.c File Reference

#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"
Include dependency graph for copy.c:

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_optionsparse_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 Documentation

#define COPYBUFSIZ   8192

Definition at line 523 of file copy.c.

Referenced by handleCopyIn().


Function Documentation

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

bool handleCopyIn ( PGconn conn,
FILE *  copystream,
bool  isbinary 
)

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

bool handleCopyOut ( PGconn conn,
FILE *  copystream 
)

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().

{
    char       *newvar;

    newvar = pg_malloc(strlen(*var) + strlen(more) + 1);
    strcpy(newvar, *var);
    strcat(newvar, more);
    free(*var);
    *var = newvar;
}