PostgreSQL Source Code  git master
copy.c
Go to the documentation of this file.
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 
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
66 {
67  if (!ptr)
68  return;
69  free(ptr->before_tofrom);
70  free(ptr->after_tofrom);
71  free(ptr->file);
72  free(ptr);
73 }
74 
75 
76 /* concatenate "more" onto "var", freeing the original value of *var */
77 static void
78 xstrcat(char **var, const char *more)
79 {
80  char *newvar;
81 
82  newvar = psprintf("%s%s", *var, more);
83  free(*var);
84  *var = newvar;
85 }
86 
87 
88 static struct copy_options *
89 parse_slash_copy(const char *args)
90 {
91  struct copy_options *result;
92  char *token;
93  const char *whitespace = " \t\n\r";
94  char nonstd_backslash = standard_strings() ? 0 : '\\';
95 
96  if (!args)
97  {
98  pg_log_error("\\copy: arguments required");
99  return NULL;
100  }
101 
102  result = pg_malloc0(sizeof(struct copy_options));
103 
104  result->before_tofrom = pg_strdup(""); /* initialize for appending */
105 
106  token = strtokx(args, whitespace, ".,()", "\"",
107  0, false, false, pset.encoding);
108  if (!token)
109  goto error;
110 
111  /* The following can be removed when we drop 7.3 syntax support */
112  if (pg_strcasecmp(token, "binary") == 0)
113  {
114  xstrcat(&result->before_tofrom, token);
115  token = strtokx(NULL, whitespace, ".,()", "\"",
116  0, false, false, pset.encoding);
117  if (!token)
118  goto error;
119  }
120 
121  /* Handle COPY (query) case */
122  if (token[0] == '(')
123  {
124  int parens = 1;
125 
126  while (parens > 0)
127  {
128  xstrcat(&result->before_tofrom, " ");
129  xstrcat(&result->before_tofrom, token);
130  token = strtokx(NULL, whitespace, "()", "\"'",
131  nonstd_backslash, true, false, pset.encoding);
132  if (!token)
133  goto error;
134  if (token[0] == '(')
135  parens++;
136  else if (token[0] == ')')
137  parens--;
138  }
139  }
140 
141  xstrcat(&result->before_tofrom, " ");
142  xstrcat(&result->before_tofrom, token);
143  token = strtokx(NULL, whitespace, ".,()", "\"",
144  0, false, false, pset.encoding);
145  if (!token)
146  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  */
152  if (token[0] == '.')
153  {
154  /* handle schema . table */
155  xstrcat(&result->before_tofrom, token);
156  token = strtokx(NULL, whitespace, ".,()", "\"",
157  0, false, false, pset.encoding);
158  if (!token)
159  goto error;
160  xstrcat(&result->before_tofrom, token);
161  token = strtokx(NULL, whitespace, ".,()", "\"",
162  0, false, false, pset.encoding);
163  if (!token)
164  goto error;
165  }
166 
167  if (token[0] == '(')
168  {
169  /* handle parenthesized column list */
170  for (;;)
171  {
172  xstrcat(&result->before_tofrom, " ");
173  xstrcat(&result->before_tofrom, token);
174  token = strtokx(NULL, whitespace, "()", "\"",
175  0, false, false, pset.encoding);
176  if (!token)
177  goto error;
178  if (token[0] == ')')
179  break;
180  }
181  xstrcat(&result->before_tofrom, " ");
182  xstrcat(&result->before_tofrom, token);
183  token = strtokx(NULL, whitespace, ".,()", "\"",
184  0, false, false, pset.encoding);
185  if (!token)
186  goto error;
187  }
188 
189  if (pg_strcasecmp(token, "from") == 0)
190  result->from = true;
191  else if (pg_strcasecmp(token, "to") == 0)
192  result->from = false;
193  else
194  goto error;
195 
196  /* { 'filename' | PROGRAM 'command' | STDIN | STDOUT | PSTDIN | PSTDOUT } */
197  token = strtokx(NULL, whitespace, ";", "'",
198  0, false, false, pset.encoding);
199  if (!token)
200  goto error;
201 
202  if (pg_strcasecmp(token, "program") == 0)
203  {
204  int toklen;
205 
206  token = strtokx(NULL, whitespace, ";", "'",
207  0, false, false, pset.encoding);
208  if (!token)
209  goto error;
210 
211  /*
212  * The shell command must be quoted. This isn't fool-proof, but
213  * catches most quoting errors.
214  */
215  toklen = strlen(token);
216  if (token[0] != '\'' || toklen < 2 || token[toklen - 1] != '\'')
217  goto error;
218 
219  strip_quotes(token, '\'', 0, pset.encoding);
220 
221  result->program = true;
222  result->file = pg_strdup(token);
223  }
224  else if (pg_strcasecmp(token, "stdin") == 0 ||
225  pg_strcasecmp(token, "stdout") == 0)
226  {
227  result->file = NULL;
228  }
229  else if (pg_strcasecmp(token, "pstdin") == 0 ||
230  pg_strcasecmp(token, "pstdout") == 0)
231  {
232  result->psql_inout = true;
233  result->file = NULL;
234  }
235  else
236  {
237  /* filename can be optionally quoted */
238  strip_quotes(token, '\'', 0, pset.encoding);
239  result->file = pg_strdup(token);
240  expand_tilde(&result->file);
241  }
242 
243  /* Collect the rest of the line (COPY options) */
244  token = strtokx(NULL, "", NULL, NULL,
245  0, false, false, pset.encoding);
246  if (token)
247  result->after_tofrom = pg_strdup(token);
248 
249  return result;
250 
251 error:
252  if (token)
253  pg_log_error("\\copy: parse error at \"%s\"", token);
254  else
255  pg_log_error("\\copy: parse error at end of line");
256  free_copy_options(result);
257 
258  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
268 do_copy(const char *args)
269 {
270  PQExpBufferData query;
271  FILE *copystream;
272  struct copy_options *options;
273  bool success;
274 
275  /* parse options */
277 
278  if (!options)
279  return false;
280 
281  /* prepare to read or write the target file */
282  if (options->file && !options->program)
283  canonicalize_path(options->file);
284 
285  if (options->from)
286  {
287  if (options->file)
288  {
289  if (options->program)
290  {
291  fflush(NULL);
292  errno = 0;
293  copystream = popen(options->file, PG_BINARY_R);
294  }
295  else
296  copystream = fopen(options->file, PG_BINARY_R);
297  }
298  else if (!options->psql_inout)
299  copystream = pset.cur_cmd_source;
300  else
301  copystream = stdin;
302  }
303  else
304  {
305  if (options->file)
306  {
307  if (options->program)
308  {
309  fflush(NULL);
311  errno = 0;
312  copystream = popen(options->file, PG_BINARY_W);
313  }
314  else
315  copystream = fopen(options->file, PG_BINARY_W);
316  }
317  else if (!options->psql_inout)
318  copystream = pset.queryFout;
319  else
320  copystream = stdout;
321  }
322 
323  if (!copystream)
324  {
325  if (options->program)
326  pg_log_error("could not execute command \"%s\": %m",
327  options->file);
328  else
329  pg_log_error("%s: %m",
330  options->file);
332  return false;
333  }
334 
335  if (!options->program)
336  {
337  struct stat st;
338  int result;
339 
340  /* make sure the specified file is not a directory */
341  if ((result = fstat(fileno(copystream), &st)) < 0)
342  pg_log_error("could not stat file \"%s\": %m",
343  options->file);
344 
345  if (result == 0 && S_ISDIR(st.st_mode))
346  pg_log_error("%s: cannot copy from/to a directory",
347  options->file);
348 
349  if (result < 0 || S_ISDIR(st.st_mode))
350  {
351  fclose(copystream);
353  return false;
354  }
355  }
356 
357  /* build the command we will send to the backend */
358  initPQExpBuffer(&query);
359  printfPQExpBuffer(&query, "COPY ");
360  appendPQExpBufferStr(&query, options->before_tofrom);
361  if (options->from)
362  appendPQExpBufferStr(&query, " FROM STDIN ");
363  else
364  appendPQExpBufferStr(&query, " TO STDOUT ");
365  if (options->after_tofrom)
366  appendPQExpBufferStr(&query, options->after_tofrom);
367 
368  /* run it like a user command, but with copystream as data source/sink */
369  pset.copyStream = copystream;
370  success = SendQuery(query.data);
371  pset.copyStream = NULL;
372  termPQExpBuffer(&query);
373 
374  if (options->file != NULL)
375  {
376  if (options->program)
377  {
378  int pclose_rc = pclose(copystream);
379 
380  if (pclose_rc != 0)
381  {
382  if (pclose_rc < 0)
383  pg_log_error("could not close pipe to external command: %m");
384  else
385  {
386  char *reason = wait_result_to_str(pclose_rc);
387 
388  pg_log_error("%s: %s", options->file,
389  reason ? reason : "");
390  free(reason);
391  }
392  success = false;
393  }
394  SetShellResultVariables(pclose_rc);
396  }
397  else
398  {
399  if (fclose(copystream) != 0)
400  {
401  pg_log_error("%s: %m", options->file);
402  success = false;
403  }
404  }
405  }
407  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
434 handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
435 {
436  bool OK = true;
437  char *buf;
438  int ret;
439 
440  for (;;)
441  {
442  ret = PQgetCopyData(conn, &buf, 0);
443 
444  if (ret < 0)
445  break; /* done or server/connection error */
446 
447  if (buf)
448  {
449  if (OK && copystream && fwrite(buf, 1, ret, copystream) != ret)
450  {
451  pg_log_error("could not write COPY data: %m");
452  /* complain only once, keep reading data from server */
453  OK = false;
454  }
455  PQfreemem(buf);
456  }
457  }
458 
459  if (OK && copystream && fflush(copystream))
460  {
461  pg_log_error("could not write COPY data: %m");
462  OK = false;
463  }
464 
465  if (ret == -2)
466  {
467  pg_log_error("COPY data transfer failed: %s", PQerrorMessage(conn));
468  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  */
483  *res = PQgetResult(conn);
485  {
487  OK = false;
488  }
489 
490  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
511 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  */
521  if (sigsetjmp(sigint_interrupt_jmp, 1) != 0)
522  {
523  /* got here with longjmp */
524 
525  /* Terminate data transfer */
527  (PQprotocolVersion(conn) < 3) ? NULL :
528  _("canceled by user"));
529 
530  OK = false;
531  goto copyin_cleanup;
532  }
533 
534  /* Prompt if interactive input */
535  if (isatty(fileno(copystream)))
536  {
537  showprompt = true;
538  if (!pset.quiet)
539  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
543  showprompt = false;
544 
545  OK = true;
546 
547  if (isbinary)
548  {
549  /* interactive input probably silly, but give one prompt anyway */
550  if (showprompt)
551  {
552  const char *prompt = get_prompt(PROMPT_COPY, NULL);
553 
554  fputs(prompt, stdout);
555  fflush(stdout);
556  }
557 
558  for (;;)
559  {
560  int buflen;
561 
562  /* enable longjmp while waiting for input */
564 
565  buflen = fread(buf, 1, COPYBUFSIZ, copystream);
566 
567  sigint_interrupt_enabled = false;
568 
569  if (buflen <= 0)
570  break;
571 
572  if (PQputCopyData(conn, buf, buflen) <= 0)
573  {
574  OK = false;
575  break;
576  }
577  }
578  }
579  else
580  {
581  bool copydone = false;
582  int buflen;
583  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  buflen = 0;
592  while (!copydone)
593  {
594  char *fgresult;
595 
596  if (at_line_begin && showprompt)
597  {
598  const char *prompt = get_prompt(PROMPT_COPY, NULL);
599 
600  fputs(prompt, stdout);
601  fflush(stdout);
602  }
603 
604  /* enable longjmp while waiting for input */
606 
607  fgresult = fgets(&buf[buflen], COPYBUFSIZ - buflen, copystream);
608 
609  sigint_interrupt_enabled = false;
610 
611  if (!fgresult)
612  copydone = true;
613  else
614  {
615  int linelen;
616 
617  linelen = strlen(fgresult);
618  buflen += linelen;
619 
620  /* current line is done? */
621  if (buf[buflen - 1] == '\n')
622  {
623  /* check for EOF marker, but not on a partial line */
624  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  if ((linelen == 3 && memcmp(fgresult, "\\.\n", 3) == 0) ||
634  (linelen == 4 && memcmp(fgresult, "\\.\r\n", 4) == 0))
635  {
636  copydone = true;
637  }
638  }
639 
640  if (copystream == pset.cur_cmd_source)
641  {
642  pset.lineno++;
643  pset.stmt_lineno++;
644  }
645  at_line_begin = true;
646  }
647  else
648  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  if (buflen >= COPYBUFSIZ - 5 || (copydone && buflen > 0))
660  {
661  if (PQputCopyData(conn, buf, buflen) <= 0)
662  {
663  OK = false;
664  break;
665  }
666 
667  buflen = 0;
668  }
669  }
670  }
671 
672  /* Check for read error */
673  if (ferror(copystream))
674  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  */
682  if (PQputCopyEnd(conn,
683  (OK || PQprotocolVersion(conn) < 3) ? NULL :
684  _("aborted because of read failure")) <= 0)
685  OK = false;
686 
687 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  */
697  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  */
712  {
713  OK = false;
714  PQclear(*res);
715  /* We can't send an error message if we're using protocol version 2 */
717  (PQprotocolVersion(conn) < 3) ? NULL :
718  _("trying to exit copy mode"));
719  }
721  {
723  OK = false;
724  }
725 
726  return OK;
727 }
void expand_tilde(char **filename)
Definition: common.c:2352
volatile sig_atomic_t sigint_interrupt_enabled
Definition: common.c:254
sigjmp_buf sigint_interrupt_jmp
Definition: common.c:256
void SetShellResultVariables(int wait_result)
Definition: common.c:462
bool standard_strings(void)
Definition: common.c:2311
bool SendQuery(const char *query)
Definition: common.c:1047
static struct copy_options * parse_slash_copy(const char *args)
Definition: copy.c:89
bool handleCopyOut(PGconn *conn, FILE *copystream, PGresult **res)
Definition: copy.c:434
bool do_copy(const char *args)
Definition: copy.c:268
static void xstrcat(char **var, const char *more)
Definition: copy.c:78
#define COPYBUFSIZ
Definition: copy.c:508
bool handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
Definition: copy.c:511
static void free_copy_options(struct copy_options *ptr)
Definition: copy.c:65
#define PG_BINARY_R
Definition: c.h:1262
#define PG_BINARY_W
Definition: c.h:1263
#define _(x)
Definition: elog.c:90
int PQprotocolVersion(const PGconn *conn)
Definition: fe-connect.c:6928
char * PQerrorMessage(const PGconn *conn)
Definition: fe-connect.c:6948
void PQfreemem(void *ptr)
Definition: fe-exec.c:3992
ExecStatusType PQresultStatus(const PGresult *res)
Definition: fe-exec.c:3371
int PQputCopyEnd(PGconn *conn, const char *errormsg)
Definition: fe-exec.c:2711
int PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
Definition: fe-exec.c:2657
PGresult * PQgetResult(PGconn *conn)
Definition: fe-exec.c:2038
int PQgetCopyData(PGconn *conn, char **buffer, int async)
Definition: fe-exec.c:2778
void * pg_malloc0(size_t size)
Definition: fe_memutils.c:53
char * pg_strdup(const char *in)
Definition: fe_memutils.c:85
void restore_sigpipe_trap(void)
Definition: print.c:3062
void disable_sigpipe_trap(void)
Definition: print.c:3039
#define free(a)
Definition: header.h:65
#define token
Definition: indent_globs.h:126
static bool success
Definition: initdb.c:186
@ PGRES_COPY_IN
Definition: libpq-fe.h:107
@ PGRES_COMMAND_OK
Definition: libpq-fe.h:100
static void const char fflush(stdout)
#define pg_log_error(...)
Definition: logging.h:106
#define pg_log_info(...)
Definition: logging.h:124
static char ** options
static char * buf
Definition: pg_test_fsync.c:73
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
void canonicalize_path(char *path)
Definition: path.c:264
void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:235
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:90
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
Definition: pqexpbuffer.c:367
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:129
char * get_prompt(promptStatus_t status, ConditionalStack cstack)
Definition: prompt.c:66
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
@ PROMPT_COPY
Definition: psqlscan.h:48
PsqlSettings pset
Definition: startup.c:32
static void error(void)
Definition: sql-dyntest.c:147
PGconn * conn
Definition: streamutil.c:54
char * strtokx(const char *s, const char *whitespace, const char *delim, const char *quote, char escape, bool e_strings, bool del_quotes, int encoding)
Definition: stringutils.c:52
void strip_quotes(char *source, char quote, char escape, int encoding)
Definition: stringutils.c:240
uint64 lineno
Definition: settings.h:115
int encoding
Definition: settings.h:83
FILE * copyStream
Definition: settings.h:87
FILE * queryFout
Definition: settings.h:84
uint64 stmt_lineno
Definition: settings.h:116
FILE * cur_cmd_source
Definition: settings.h:109
bool program
Definition: copy.c:58
bool from
Definition: copy.c:60
bool psql_inout
Definition: copy.c:59
char * file
Definition: copy.c:57
char * before_tofrom
Definition: copy.c:55
char * after_tofrom
Definition: copy.c:56
unsigned short st_mode
Definition: win32_port.h:268
char * wait_result_to_str(int exitstatus)
Definition: wait_error.c:33
#define S_ISDIR(m)
Definition: win32_port.h:325
#define fstat
Definition: win32_port.h:283