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