YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/postgres/src/bin/psql/copy.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * psql - the PostgreSQL interactive terminal
3
 *
4
 * Copyright (c) 2000-2018, PostgreSQL Global Development Group
5
 *
6
 * src/bin/psql/copy.c
7
 */
8
#include "postgres_fe.h"
9
#include "copy.h"
10
11
#include <signal.h>
12
#include <sys/stat.h>
13
#ifndef WIN32
14
#include <unistd.h>       /* for isatty */
15
#else
16
#include <io.h>         /* I think */
17
#endif
18
19
#include "libpq-fe.h"
20
#include "pqexpbuffer.h"
21
22
#include "settings.h"
23
#include "common.h"
24
#include "prompt.h"
25
#include "stringutils.h"
26
27
28
/*
29
 * parse_slash_copy
30
 * -- parses \copy command line
31
 *
32
 * The documented syntax is:
33
 *  \copy tablename [(columnlist)] from|to filename [options]
34
 *  \copy ( query stmt ) to filename [options]
35
 *
36
 * where 'filename' can be one of the following:
37
 *  '<file path>' | PROGRAM '<command>' | stdin | stdout | pstdout | pstdout
38
 * and 'query' can be one of the following:
39
 *  SELECT | UPDATE | INSERT | DELETE
40
 *
41
 * An undocumented fact is that you can still write BINARY before the
42
 * tablename; this is a hangover from the pre-7.3 syntax.  The options
43
 * syntax varies across backend versions, but we avoid all that mess
44
 * by just transmitting the stuff after the filename literally.
45
 *
46
 * table name can be double-quoted and can have a schema part.
47
 * column names can be double-quoted.
48
 * filename can be single-quoted like SQL literals.
49
 * command must be single-quoted like SQL literals.
50
 *
51
 * returns a malloc'ed structure with the options, or NULL on parsing error
52
 */
53
54
struct copy_options
55
{
56
  char     *before_tofrom;  /* COPY string before TO/FROM */
57
  char     *after_tofrom; /* COPY string after TO/FROM filename */
58
  char     *file;     /* NULL = stdin/stdout */
59
  bool    program;    /* is 'file' a program to popen? */
60
  bool    psql_inout;   /* true = use psql stdin/stdout */
61
  bool    from;     /* true = FROM, false = TO */
62
};
63
64
65
static void
66
free_copy_options(struct copy_options *ptr)
67
6
{
68
6
  if (!ptr)
69
0
    return;
70
6
  free(ptr->before_tofrom);
71
6
  free(ptr->after_tofrom);
72
6
  free(ptr->file);
73
6
  free(ptr);
74
6
}
75
76
77
/* concatenate "more" onto "var", freeing the original value of *var */
78
static void
79
xstrcat(char **var, const char *more)
80
12
{
81
12
  char     *newvar;
82
83
12
  newvar = psprintf("%s%s", *var, more);
84
12
  free(*var);
85
12
  *var = newvar;
86
12
}
87
88
89
static struct copy_options *
90
parse_slash_copy(const char *args)
91
6
{
92
6
  struct copy_options *result;
93
6
  char     *token;
94
6
  const char *whitespace = " \t\n\r";
95
6
  char    nonstd_backslash = standard_strings() ? 0 : '\\';
96
97
6
  if (!args)
98
0
  {
99
0
    psql_error("\\copy: arguments required\n");
100
0
    return NULL;
101
0
  }
102
103
6
  result = pg_malloc0(sizeof(struct copy_options));
104
105
6
  result->before_tofrom = pg_strdup("");  /* initialize for appending */
106
107
6
  token = strtokx(args, whitespace, ".,()", "\"",
108
6
          0, false, false, pset.encoding);
109
6
  if (!token)
110
0
    goto error;
111
112
  /* The following can be removed when we drop 7.3 syntax support */
113
6
  if (pg_strcasecmp(token, "binary") == 0)
114
0
  {
115
0
    xstrcat(&result->before_tofrom, token);
116
0
    token = strtokx(NULL, whitespace, ".,()", "\"",
117
0
            0, false, false, pset.encoding);
118
0
    if (!token)
119
0
      goto error;
120
6
  }
121
122
  /* Handle COPY (query) case */
123
6
  if (token[0] == '(')
124
0
  {
125
0
    int     parens = 1;
126
127
0
    while (parens > 0)
128
0
    {
129
0
      xstrcat(&result->before_tofrom, " ");
130
0
      xstrcat(&result->before_tofrom, token);
131
0
      token = strtokx(NULL, whitespace, "()", "\"'",
132
0
              nonstd_backslash, true, false, pset.encoding);
133
0
      if (!token)
134
0
        goto error;
135
0
      if (token[0] == '(')
136
0
        parens++;
137
0
      else if (token[0] == ')')
138
0
        parens--;
139
0
    }
140
0
  }
141
142
6
  xstrcat(&result->before_tofrom, " ");
143
6
  xstrcat(&result->before_tofrom, token);
144
6
  token = strtokx(NULL, whitespace, ".,()", "\"",
145
6
          0, false, false, pset.encoding);
146
6
  if (!token)
147
0
    goto error;
148
149
  /*
150
   * strtokx() will not have returned a multi-character token starting with
151
   * '.', so we don't need strcmp() here.  Likewise for '(', etc, below.
152
   */
153
6
  if (token[0] == '.')
154
0
  {
155
    /* handle schema . table */
156
0
    xstrcat(&result->before_tofrom, token);
157
0
    token = strtokx(NULL, whitespace, ".,()", "\"",
158
0
            0, false, false, pset.encoding);
159
0
    if (!token)
160
0
      goto error;
161
0
    xstrcat(&result->before_tofrom, token);
162
0
    token = strtokx(NULL, whitespace, ".,()", "\"",
163
0
            0, false, false, pset.encoding);
164
0
    if (!token)
165
0
      goto error;
166
6
  }
167
168
6
  if (token[0] == '(')
169
0
  {
170
    /* handle parenthesized column list */
171
0
    for (;;)
172
0
    {
173
0
      xstrcat(&result->before_tofrom, " ");
174
0
      xstrcat(&result->before_tofrom, token);
175
0
      token = strtokx(NULL, whitespace, "()", "\"",
176
0
              0, false, false, pset.encoding);
177
0
      if (!token)
178
0
        goto error;
179
0
      if (token[0] == ')')
180
0
        break;
181
0
    }
182
0
    xstrcat(&result->before_tofrom, " ");
183
0
    xstrcat(&result->before_tofrom, token);
184
0
    token = strtokx(NULL, whitespace, ".,()", "\"",
185
0
            0, false, false, pset.encoding);
186
0
    if (!token)
187
0
      goto error;
188
6
  }
189
190
6
  if (pg_strcasecmp(token, "from") == 0)
191
6
    result->from = true;
192
0
  else if (pg_strcasecmp(token, "to") == 0)
193
0
    result->from = false;
194
0
  else
195
0
    goto error;
196
197
  /* { 'filename' | PROGRAM 'command' | STDIN | STDOUT | PSTDIN | PSTDOUT } */
198
6
  token = strtokx(NULL, whitespace, ";", "'",
199
6
          0, false, false, pset.encoding);
200
6
  if (!token)
201
0
    goto error;
202
203
6
  if (pg_strcasecmp(token, "program") == 0)
204
0
  {
205
0
    int     toklen;
206
207
0
    token = strtokx(NULL, whitespace, ";", "'",
208
0
            0, false, false, pset.encoding);
209
0
    if (!token)
210
0
      goto error;
211
212
    /*
213
     * The shell command must be quoted. This isn't fool-proof, but
214
     * catches most quoting errors.
215
     */
216
0
    toklen = strlen(token);
217
0
    if (token[0] != '\'' || toklen < 2 || token[toklen - 1] != '\'')
218
0
      goto error;
219
220
0
    strip_quotes(token, '\'', 0, pset.encoding);
221
222
0
    result->program = true;
223
0
    result->file = pg_strdup(token);
224
0
  }
225
6
  else if (pg_strcasecmp(token, "stdin") == 0 ||
226
6
       pg_strcasecmp(token, "stdout") == 0)
227
0
  {
228
0
    result->file = NULL;
229
0
  }
230
6
  else if (pg_strcasecmp(token, "pstdin") == 0 ||
231
6
       pg_strcasecmp(token, "pstdout") == 0)
232
0
  {
233
0
    result->psql_inout = true;
234
0
    result->file = NULL;
235
0
  }
236
6
  else
237
6
  {
238
    /* filename can be optionally quoted */
239
6
    strip_quotes(token, '\'', 0, pset.encoding);
240
6
    result->file = pg_strdup(token);
241
6
    expand_tilde(&result->file);
242
6
  }
243
244
  /* Collect the rest of the line (COPY options) */
245
6
  token = strtokx(NULL, "", NULL, NULL,
246
6
          0, false, false, pset.encoding);
247
6
  if (token)
248
0
    result->after_tofrom = pg_strdup(token);
249
250
6
  return result;
251
252
0
error:
253
0
  if (token)
254
0
    psql_error("\\copy: parse error at \"%s\"\n", token);
255
0
  else
256
0
    psql_error("\\copy: parse error at end of line\n");
257
0
  free_copy_options(result);
258
259
0
  return NULL;
260
6
}
261
262
263
/*
264
 * Execute a \copy command (frontend copy). We have to open a file (or execute
265
 * a command), then submit a COPY query to the backend and either feed it data
266
 * from the file or route its response into the file.
267
 */
268
bool
269
do_copy(const char *args)
270
6
{
271
6
  PQExpBufferData query;
272
6
  FILE     *copystream;
273
6
  struct copy_options *options;
274
6
  bool    success;
275
276
  /* parse options */
277
6
  options = parse_slash_copy(args);
278
279
6
  if (!options)
280
0
    return false;
281
282
  /* prepare to read or write the target file */
283
6
  if (options->file && !options->program)
284
6
    canonicalize_path(options->file);
285
286
6
  if (options->from)
287
6
  {
288
6
    if (options->file)
289
6
    {
290
6
      if (options->program)
291
0
      {
292
0
        fflush(stdout);
293
0
        fflush(stderr);
294
0
        errno = 0;
295
0
        copystream = popen(options->file, PG_BINARY_R);
296
0
      }
297
6
      else
298
6
        copystream = fopen(options->file, PG_BINARY_R);
299
6
    }
300
0
    else if (!options->psql_inout)
301
0
      copystream = pset.cur_cmd_source;
302
0
    else
303
0
      copystream = stdin;
304
6
  }
305
0
  else
306
0
  {
307
0
    if (options->file)
308
0
    {
309
0
      if (options->program)
310
0
      {
311
0
        fflush(stdout);
312
0
        fflush(stderr);
313
0
        errno = 0;
314
0
        disable_sigpipe_trap();
315
0
        copystream = popen(options->file, PG_BINARY_W);
316
0
      }
317
0
      else
318
0
        copystream = fopen(options->file, PG_BINARY_W);
319
0
    }
320
0
    else if (!options->psql_inout)
321
0
      copystream = pset.queryFout;
322
0
    else
323
0
      copystream = stdout;
324
0
  }
325
326
6
  if (!copystream)
327
0
  {
328
0
    if (options->program)
329
0
      psql_error("could not execute command \"%s\": %s\n",
330
0
             options->file, strerror(errno));
331
0
    else
332
0
      psql_error("%s: %s\n",
333
0
             options->file, strerror(errno));
334
0
    free_copy_options(options);
335
0
    return false;
336
0
  }
337
338
6
  if (!options->program)
339
6
  {
340
6
    struct stat st;
341
6
    int     result;
342
343
    /* make sure the specified file is not a directory */
344
6
    if ((result = fstat(fileno(copystream), &st)) < 0)
345
0
      psql_error("could not stat file \"%s\": %s\n",
346
0
             options->file, strerror(errno));
347
348
6
    if (result == 0 && S_ISDIR(st.st_mode))
349
0
      psql_error("%s: cannot copy from/to a directory\n",
350
0
             options->file);
351
352
6
    if (result < 0 || S_ISDIR(st.st_mode))
353
0
    {
354
0
      fclose(copystream);
355
0
      free_copy_options(options);
356
0
      return false;
357
0
    }
358
6
  }
359
360
  /* build the command we will send to the backend */
361
6
  initPQExpBuffer(&query);
362
6
  printfPQExpBuffer(&query, "COPY ");
363
6
  appendPQExpBufferStr(&query, options->before_tofrom);
364
6
  if (options->from)
365
6
    appendPQExpBufferStr(&query, " FROM STDIN ");
366
0
  else
367
0
    appendPQExpBufferStr(&query, " TO STDOUT ");
368
6
  if (options->after_tofrom)
369
0
    appendPQExpBufferStr(&query, options->after_tofrom);
370
371
  /* run it like a user command, but with copystream as data source/sink */
372
6
  pset.copyStream = copystream;
373
6
  success = SendQuery(query.data);
374
6
  pset.copyStream = NULL;
375
6
  termPQExpBuffer(&query);
376
377
6
  if (options->file != NULL)
378
6
  {
379
6
    if (options->program)
380
0
    {
381
0
      int     pclose_rc = pclose(copystream);
382
383
0
      if (pclose_rc != 0)
384
0
      {
385
0
        if (pclose_rc < 0)
386
0
          psql_error("could not close pipe to external command: %s\n",
387
0
                 strerror(errno));
388
0
        else
389
0
        {
390
0
          char     *reason = wait_result_to_str(pclose_rc);
391
392
0
          psql_error("%s: %s\n", options->file,
393
0
                 reason ? reason : "");
394
0
          if (reason)
395
0
            free(reason);
396
0
        }
397
0
        success = false;
398
0
      }
399
0
      restore_sigpipe_trap();
400
0
    }
401
6
    else
402
6
    {
403
6
      if (fclose(copystream) != 0)
404
0
      {
405
0
        psql_error("%s: %s\n", options->file, strerror(errno));
406
0
        success = false;
407
0
      }
408
6
    }
409
6
  }
410
6
  free_copy_options(options);
411
6
  return success;
412
6
}
413
414
415
/*
416
 * Functions for handling COPY IN/OUT data transfer.
417
 *
418
 * If you want to use COPY TO STDOUT/FROM STDIN in your application,
419
 * this is the code to steal ;)
420
 */
421
422
/*
423
 * handleCopyOut
424
 * receives data as a result of a COPY ... TO STDOUT command
425
 *
426
 * conn should be a database connection that you just issued COPY TO on
427
 * and got back a PGRES_COPY_OUT result.
428
 *
429
 * copystream is the file stream for the data to go to.
430
 * copystream can be NULL to eat the data without writing it anywhere.
431
 *
432
 * The final status for the COPY is returned into *res (but note
433
 * we already reported the error, if it's not a success result).
434
 *
435
 * result is true if successful, false if not.
436
 */
437
bool
438
handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
439
0
{
440
0
  bool    OK = true;
441
0
  char     *buf;
442
0
  int     ret;
443
444
0
  for (;;)
445
0
  {
446
0
    ret = PQgetCopyData(conn, &buf, 0);
447
448
0
    if (ret < 0)
449
0
      break;       /* done or server/connection error */
450
451
0
    if (buf)
452
0
    {
453
0
      if (OK && copystream && fwrite(buf, 1, ret, copystream) != ret)
454
0
      {
455
0
        psql_error("could not write COPY data: %s\n",
456
0
               strerror(errno));
457
        /* complain only once, keep reading data from server */
458
0
        OK = false;
459
0
      }
460
0
      PQfreemem(buf);
461
0
    }
462
0
  }
463
464
0
  if (OK && copystream && fflush(copystream))
465
0
  {
466
0
    psql_error("could not write COPY data: %s\n",
467
0
           strerror(errno));
468
0
    OK = false;
469
0
  }
470
471
0
  if (ret == -2)
472
0
  {
473
0
    psql_error("COPY data transfer failed: %s", PQerrorMessage(conn));
474
0
    OK = false;
475
0
  }
476
477
  /*
478
   * Check command status and return to normal libpq state.
479
   *
480
   * If for some reason libpq is still reporting PGRES_COPY_OUT state, we
481
   * would like to forcibly exit that state, since our caller would be
482
   * unable to distinguish that situation from reaching the next COPY in a
483
   * command string that happened to contain two consecutive COPY TO STDOUT
484
   * commands.  However, libpq provides no API for doing that, and in
485
   * principle it's a libpq bug anyway if PQgetCopyData() returns -1 or -2
486
   * but hasn't exited COPY_OUT state internally.  So we ignore the
487
   * possibility here.
488
   */
489
0
  *res = PQgetResult(conn);
490
0
  if (PQresultStatus(*res) != PGRES_COMMAND_OK)
491
0
  {
492
0
    psql_error("%s", PQerrorMessage(conn));
493
0
    OK = false;
494
0
  }
495
496
0
  return OK;
497
0
}
498
499
/*
500
 * handleCopyIn
501
 * sends data to complete a COPY ... FROM STDIN command
502
 *
503
 * conn should be a database connection that you just issued COPY FROM on
504
 * and got back a PGRES_COPY_IN result.
505
 * copystream is the file stream to read the data from.
506
 * isbinary can be set from PQbinaryTuples().
507
 * The final status for the COPY is returned into *res (but note
508
 * we already reported the error, if it's not a success result).
509
 *
510
 * result is true if successful, false if not.
511
 */
512
513
/* read chunk size for COPY IN - size is not critical */
514
0
#define COPYBUFSIZ 8192
515
516
bool
517
handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
518
20
{
519
20
  bool    OK;
520
20
  char    buf[COPYBUFSIZ];
521
20
  bool    showprompt;
522
523
  /*
524
   * Establish longjmp destination for exiting from wait-for-input. (This is
525
   * only effective while sigint_interrupt_enabled is TRUE.)
526
   */
527
20
  if (sigsetjmp(sigint_interrupt_jmp, 1) != 0)
528
0
  {
529
    /* got here with longjmp */
530
531
    /* Terminate data transfer */
532
0
    PQputCopyEnd(conn,
533
0
           (PQprotocolVersion(conn) < 3) ? NULL :
534
0
           _("canceled by user"));
535
536
0
    OK = false;
537
0
    goto copyin_cleanup;
538
0
  }
539
540
  /* Prompt if interactive input */
541
20
  if (isatty(fileno(copystream)))
542
0
  {
543
0
    showprompt = true;
544
0
    if (!pset.quiet)
545
0
      puts(_("Enter data to be copied followed by a newline.\n"
546
0
           "End with a backslash and a period on a line by itself, or an EOF signal."));
547
0
  }
548
20
  else
549
20
    showprompt = false;
550
551
20
  OK = true;
552
553
20
  if (isbinary)
554
0
  {
555
    /* interactive input probably silly, but give one prompt anyway */
556
0
    if (showprompt)
557
0
    {
558
0
      const char *prompt = get_prompt(PROMPT_COPY, NULL);
559
560
0
      fputs(prompt, stdout);
561
0
      fflush(stdout);
562
0
    }
563
564
0
    for (;;)
565
0
    {
566
0
      int     buflen;
567
568
      /* enable longjmp while waiting for input */
569
0
      sigint_interrupt_enabled = true;
570
571
0
      buflen = fread(buf, 1, COPYBUFSIZ, copystream);
572
573
0
      sigint_interrupt_enabled = false;
574
575
0
      if (buflen <= 0)
576
0
        break;
577
578
0
      if (PQputCopyData(conn, buf, buflen) <= 0)
579
0
      {
580
0
        OK = false;
581
0
        break;
582
0
      }
583
0
    }
584
0
  }
585
20
  else
586
20
  {
587
20
    bool    copydone = false;
588
589
12.8k
    while (!copydone)
590
12.8k
    {           /* for each input line ... */
591
12.8k
      bool    firstload;
592
12.8k
      bool    linedone;
593
594
12.8k
      if (showprompt)
595
0
      {
596
0
        const char *prompt = get_prompt(PROMPT_COPY, NULL);
597
598
0
        fputs(prompt, stdout);
599
0
        fflush(stdout);
600
0
      }
601
602
12.8k
      firstload = true;
603
12.8k
      linedone = false;
604
605
25.6k
      while (!linedone)
606
12.8k
      {         /* for each bufferload in line ... */
607
12.8k
        int     linelen;
608
12.8k
        char     *fgresult;
609
610
        /* enable longjmp while waiting for input */
611
12.8k
        sigint_interrupt_enabled = true;
612
613
12.8k
        fgresult = fgets(buf, sizeof(buf), copystream);
614
615
12.8k
        sigint_interrupt_enabled = false;
616
617
12.8k
        if (!fgresult)
618
5
        {
619
5
          copydone = true;
620
5
          break;
621
5
        }
622
623
12.8k
        linelen = strlen(buf);
624
625
        /* current line is done? */
626
12.8k
        if (linelen > 0 && buf[linelen - 1] == '\n')
627
12.8k
          linedone = true;
628
629
        /* check for EOF marker, but not on a partial line */
630
12.8k
        if (firstload)
631
12.8k
        {
632
          /*
633
           * This code erroneously assumes '\.' on a line alone
634
           * inside a quoted CSV string terminates the \copy.
635
           * http://www.postgresql.org/message-id/E1TdNVQ-0001ju-GO@wrigleys.postgresql.org
636
           */
637
12.8k
          if (strcmp(buf, "\\.\n") == 0 ||
638
12.8k
            strcmp(buf, "\\.\r\n") == 0)
639
15
          {
640
15
            copydone = true;
641
15
            break;
642
15
          }
643
644
12.8k
          firstload = false;
645
12.8k
        }
646
647
12.8k
        if (PQputCopyData(conn, buf, linelen) <= 0)
648
0
        {
649
0
          OK = false;
650
0
          copydone = true;
651
0
          break;
652
0
        }
653
12.8k
      }
654
655
12.8k
      if (copystream == pset.cur_cmd_source)
656
44
      {
657
44
        pset.lineno++;
658
44
        pset.stmt_lineno++;
659
44
      }
660
12.8k
    }
661
20
  }
662
663
  /* Check for read error */
664
20
  if (ferror(copystream))
665
0
    OK = false;
666
667
  /*
668
   * Terminate data transfer.  We can't send an error message if we're using
669
   * protocol version 2.
670
   */
671
20
  if (PQputCopyEnd(conn,
672
20
           (OK || PQprotocolVersion(conn) < 3) ? NULL :
673
20
           _("aborted because of read failure")) <= 0)
674
0
    OK = false;
675
676
20
copyin_cleanup:
677
678
  /*
679
   * Clear the EOF flag on the stream, in case copying ended due to an EOF
680
   * signal.  This allows an interactive TTY session to perform another COPY
681
   * FROM STDIN later.  (In non-STDIN cases, we're about to close the file
682
   * anyway, so it doesn't matter.)  Although we don't ever test the flag
683
   * with feof(), some fread() implementations won't read more data if it's
684
   * set.  This also clears the error flag, but we already checked that.
685
   */
686
20
  clearerr(copystream);
687
688
  /*
689
   * Check command status and return to normal libpq state.
690
   *
691
   * We do not want to return with the status still PGRES_COPY_IN: our
692
   * caller would be unable to distinguish that situation from reaching the
693
   * next COPY in a command string that happened to contain two consecutive
694
   * COPY FROM STDIN commands.  We keep trying PQputCopyEnd() in the hope
695
   * it'll work eventually.  (What's actually likely to happen is that in
696
   * attempting to flush the data, libpq will eventually realize that the
697
   * connection is lost.  But that's fine; it will get us out of COPY_IN
698
   * state, which is what we need.)
699
   */
700
20
  while (*res = PQgetResult(conn), PQresultStatus(*res) == PGRES_COPY_IN)
701
0
  {
702
0
    OK = false;
703
0
    PQclear(*res);
704
    /* We can't send an error message if we're using protocol version 2 */
705
0
    PQputCopyEnd(conn,
706
0
           (PQprotocolVersion(conn) < 3) ? NULL :
707
0
           _("trying to exit copy mode"));
708
0
  }
709
20
  if (PQresultStatus(*res) != PGRES_COMMAND_OK)
710
2
  {
711
2
    psql_error("%s", PQerrorMessage(conn));
712
2
    OK = false;
713
2
  }
714
715
20
  return OK;
716
20
}