PostgreSQL Source Code  git master
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
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 "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
 

Macros

#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, PGresult **res)
 
bool handleCopyIn (PGconn *conn, FILE *copystream, bool isbinary, PGresult **res)
 

Macro Definition Documentation

#define COPYBUFSIZ   8192

Definition at line 511 of file copy.c.

Referenced by handleCopyIn().

Function Documentation

bool do_copy ( const char *  args)

Definition at line 269 of file copy.c.

References copy_options::after_tofrom, appendPQExpBufferStr(), copy_options::before_tofrom, canonicalize_path(), _psqlSettings::copyStream, _psqlSettings::cur_cmd_source, PQExpBufferData::data, disable_sigpipe_trap(), copy_options::file, free, free_copy_options(), copy_options::from, initPQExpBuffer(), NULL, options, parse_slash_copy(), PG_BINARY_R, PG_BINARY_W, printfPQExpBuffer(), copy_options::program, pset, psql_error(), copy_options::psql_inout, _psqlSettings::queryFout, restore_sigpipe_trap(), SendQuery(), strerror(), success, termPQExpBuffer(), and wait_result_to_str().

Referenced by exec_command().

270 {
271  PQExpBufferData query;
272  FILE *copystream;
273  struct copy_options *options;
274  bool success;
275 
276  /* parse options */
277  options = parse_slash_copy(args);
278 
279  if (!options)
280  return false;
281 
282  /* prepare to read or write the target file */
283  if (options->file && !options->program)
284  canonicalize_path(options->file);
285 
286  if (options->from)
287  {
288  if (options->file)
289  {
290  if (options->program)
291  {
292  fflush(stdout);
293  fflush(stderr);
294  errno = 0;
295  copystream = popen(options->file, PG_BINARY_R);
296  }
297  else
298  copystream = fopen(options->file, PG_BINARY_R);
299  }
300  else if (!options->psql_inout)
301  copystream = pset.cur_cmd_source;
302  else
303  copystream = stdin;
304  }
305  else
306  {
307  if (options->file)
308  {
309  if (options->program)
310  {
311  fflush(stdout);
312  fflush(stderr);
313  errno = 0;
315  copystream = popen(options->file, PG_BINARY_W);
316  }
317  else
318  copystream = fopen(options->file, PG_BINARY_W);
319  }
320  else if (!options->psql_inout)
321  copystream = pset.queryFout;
322  else
323  copystream = stdout;
324  }
325 
326  if (!copystream)
327  {
328  if (options->program)
329  psql_error("could not execute command \"%s\": %s\n",
330  options->file, strerror(errno));
331  else
332  psql_error("%s: %s\n",
333  options->file, strerror(errno));
334  free_copy_options(options);
335  return false;
336  }
337 
338  if (!options->program)
339  {
340  struct stat st;
341  int result;
342 
343  /* make sure the specified file is not a directory */
344  if ((result = fstat(fileno(copystream), &st)) < 0)
345  psql_error("could not stat file \"%s\": %s\n",
346  options->file, strerror(errno));
347 
348  if (result == 0 && S_ISDIR(st.st_mode))
349  psql_error("%s: cannot copy from/to a directory\n",
350  options->file);
351 
352  if (result < 0 || S_ISDIR(st.st_mode))
353  {
354  fclose(copystream);
355  free_copy_options(options);
356  return false;
357  }
358  }
359 
360  /* build the command we will send to the backend */
361  initPQExpBuffer(&query);
362  printfPQExpBuffer(&query, "COPY ");
363  appendPQExpBufferStr(&query, options->before_tofrom);
364  if (options->from)
365  appendPQExpBufferStr(&query, " FROM STDIN ");
366  else
367  appendPQExpBufferStr(&query, " TO STDOUT ");
368  if (options->after_tofrom)
369  appendPQExpBufferStr(&query, options->after_tofrom);
370 
371  /* run it like a user command, but with copystream as data source/sink */
372  pset.copyStream = copystream;
373  success = SendQuery(query.data);
374  pset.copyStream = NULL;
375  termPQExpBuffer(&query);
376 
377  if (options->file != NULL)
378  {
379  if (options->program)
380  {
381  int pclose_rc = pclose(copystream);
382 
383  if (pclose_rc != 0)
384  {
385  if (pclose_rc < 0)
386  psql_error("could not close pipe to external command: %s\n",
387  strerror(errno));
388  else
389  {
390  char *reason = wait_result_to_str(pclose_rc);
391 
392  psql_error("%s: %s\n", options->file,
393  reason ? reason : "");
394  if (reason)
395  free(reason);
396  }
397  success = false;
398  }
400  }
401  else
402  {
403  if (fclose(copystream) != 0)
404  {
405  psql_error("%s: %s\n", options->file, strerror(errno));
406  success = false;
407  }
408  }
409  }
410  free_copy_options(options);
411  return success;
412 }
void printfPQExpBuffer(PQExpBuffer str, const char *fmt,...)
Definition: pqexpbuffer.c:234
PsqlSettings pset
Definition: startup.c:37
void disable_sigpipe_trap(void)
Definition: print.c:2802
void termPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:128
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
Definition: pqexpbuffer.c:385
static void free_copy_options(struct copy_options *ptr)
Definition: copy.c:66
FILE * queryFout
Definition: settings.h:84
void canonicalize_path(char *path)
Definition: path.c:254
#define PG_BINARY_W
Definition: c.h:1040
#define PG_BINARY_R
Definition: c.h:1039
FILE * copyStream
Definition: settings.h:87
char * wait_result_to_str(int exitstatus)
Definition: wait_error.c:34
static struct copy_options * parse_slash_copy(const char *args)
Definition: copy.c:90
FILE * cur_cmd_source
Definition: settings.h:102
char * before_tofrom
Definition: copy.c:56
bool psql_inout
Definition: copy.c:60
static bool success
Definition: pg_basebackup.c:99
bool program
Definition: copy.c:59
void restore_sigpipe_trap(void)
Definition: print.c:2825
bool from
Definition: copy.c:61
static char ** options
void psql_error(const char *fmt,...)
Definition: common.c:177
#define free(a)
Definition: header.h:60
char * after_tofrom
Definition: copy.c:57
#define NULL
Definition: c.h:226
const char * strerror(int errnum)
Definition: strerror.c:19
bool SendQuery(const char *query)
Definition: common.c:1183
void initPQExpBuffer(PQExpBuffer str)
Definition: pqexpbuffer.c:89
char * file
Definition: copy.c:58
static void free_copy_options ( struct copy_options ptr)
static

Definition at line 66 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().

67 {
68  if (!ptr)
69  return;
70  free(ptr->before_tofrom);
71  free(ptr->after_tofrom);
72  free(ptr->file);
73  free(ptr);
74 }
char * before_tofrom
Definition: copy.c:56
#define free(a)
Definition: header.h:60
char * after_tofrom
Definition: copy.c:57
char * file
Definition: copy.c:58
bool handleCopyIn ( PGconn conn,
FILE *  copystream,
bool  isbinary,
PGresult **  res 
)

Definition at line 514 of file copy.c.

References _, buf, COPYBUFSIZ, _psqlSettings::cur_cmd_source, get_prompt(), _psqlSettings::lineno, NULL, PGRES_COMMAND_OK, PGRES_COPY_IN, PQclear(), PQerrorMessage(), PQgetResult(), PQprotocolVersion(), PQputCopyData(), PQputCopyEnd(), PQresultStatus(), PROMPT_COPY, pset, psql_error(), _psqlSettings::quiet, sigint_interrupt_enabled, sigint_interrupt_jmp, and _psqlSettings::stmt_lineno.

Referenced by ProcessResult().

515 {
516  bool OK;
517  char buf[COPYBUFSIZ];
518  bool showprompt;
519 
520  /*
521  * Establish longjmp destination for exiting from wait-for-input. (This is
522  * only effective while sigint_interrupt_enabled is TRUE.)
523  */
524  if (sigsetjmp(sigint_interrupt_jmp, 1) != 0)
525  {
526  /* got here with longjmp */
527 
528  /* Terminate data transfer */
529  PQputCopyEnd(conn,
530  (PQprotocolVersion(conn) < 3) ? NULL :
531  _("canceled by user"));
532 
533  OK = false;
534  goto copyin_cleanup;
535  }
536 
537  /* Prompt if interactive input */
538  if (isatty(fileno(copystream)))
539  {
540  showprompt = true;
541  if (!pset.quiet)
542  puts(_("Enter data to be copied followed by a newline.\n"
543  "End with a backslash and a period on a line by itself."));
544  }
545  else
546  showprompt = false;
547 
548  OK = true;
549 
550  if (isbinary)
551  {
552  /* interactive input probably silly, but give one prompt anyway */
553  if (showprompt)
554  {
555  const char *prompt = get_prompt(PROMPT_COPY);
556 
557  fputs(prompt, stdout);
558  fflush(stdout);
559  }
560 
561  for (;;)
562  {
563  int buflen;
564 
565  /* enable longjmp while waiting for input */
567 
568  buflen = fread(buf, 1, COPYBUFSIZ, copystream);
569 
570  sigint_interrupt_enabled = false;
571 
572  if (buflen <= 0)
573  break;
574 
575  if (PQputCopyData(conn, buf, buflen) <= 0)
576  {
577  OK = false;
578  break;
579  }
580  }
581  }
582  else
583  {
584  bool copydone = false;
585 
586  while (!copydone)
587  { /* for each input line ... */
588  bool firstload;
589  bool linedone;
590 
591  if (showprompt)
592  {
593  const char *prompt = get_prompt(PROMPT_COPY);
594 
595  fputs(prompt, stdout);
596  fflush(stdout);
597  }
598 
599  firstload = true;
600  linedone = false;
601 
602  while (!linedone)
603  { /* for each bufferload in line ... */
604  int linelen;
605  char *fgresult;
606 
607  /* enable longjmp while waiting for input */
609 
610  fgresult = fgets(buf, sizeof(buf), copystream);
611 
612  sigint_interrupt_enabled = false;
613 
614  if (!fgresult)
615  {
616  copydone = true;
617  break;
618  }
619 
620  linelen = strlen(buf);
621 
622  /* current line is done? */
623  if (linelen > 0 && buf[linelen - 1] == '\n')
624  linedone = true;
625 
626  /* check for EOF marker, but not on a partial line */
627  if (firstload)
628  {
629  /*
630  * This code erroneously assumes '\.' on a line alone
631  * inside a quoted CSV string terminates the \copy.
632  * http://www.postgresql.org/message-id/E1TdNVQ-0001ju-GO@w
633  * rigleys.postgresql.org
634  */
635  if (strcmp(buf, "\\.\n") == 0 ||
636  strcmp(buf, "\\.\r\n") == 0)
637  {
638  copydone = true;
639  break;
640  }
641 
642  firstload = false;
643  }
644 
645  if (PQputCopyData(conn, buf, linelen) <= 0)
646  {
647  OK = false;
648  copydone = true;
649  break;
650  }
651  }
652 
653  if (copystream == pset.cur_cmd_source)
654  {
655  pset.lineno++;
656  pset.stmt_lineno++;
657  }
658  }
659  }
660 
661  /* Check for read error */
662  if (ferror(copystream))
663  OK = false;
664 
665  /*
666  * Terminate data transfer. We can't send an error message if we're using
667  * protocol version 2.
668  */
669  if (PQputCopyEnd(conn,
670  (OK || PQprotocolVersion(conn) < 3) ? NULL :
671  _("aborted because of read failure")) <= 0)
672  OK = false;
673 
674 copyin_cleanup:
675 
676  /*
677  * Check command status and return to normal libpq state.
678  *
679  * We do not want to return with the status still PGRES_COPY_IN: our
680  * caller would be unable to distinguish that situation from reaching the
681  * next COPY in a command string that happened to contain two consecutive
682  * COPY FROM STDIN commands. We keep trying PQputCopyEnd() in the hope
683  * it'll work eventually. (What's actually likely to happen is that in
684  * attempting to flush the data, libpq will eventually realize that the
685  * connection is lost. But that's fine; it will get us out of COPY_IN
686  * state, which is what we need.)
687  */
688  while (*res = PQgetResult(conn), PQresultStatus(*res) == PGRES_COPY_IN)
689  {
690  OK = false;
691  PQclear(*res);
692  /* We can't send an error message if we're using protocol version 2 */
693  PQputCopyEnd(conn,
694  (PQprotocolVersion(conn) < 3) ? NULL :
695  _("trying to exit copy mode"));
696  }
697  if (PQresultStatus(*res) != PGRES_COMMAND_OK)
698  {
699  psql_error("%s", PQerrorMessage(conn));
700  OK = false;
701  }
702 
703  return OK;
704 }
int PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
Definition: fe-exec.c:2221
char * PQerrorMessage(const PGconn *conn)
Definition: fe-connect.c:5960
char * get_prompt(promptStatus_t status)
Definition: prompt.c:69
volatile bool sigint_interrupt_enabled
Definition: common.c:230
PsqlSettings pset
Definition: startup.c:37
int PQputCopyEnd(PGconn *conn, const char *errormsg)
Definition: fe-exec.c:2288
ExecStatusType PQresultStatus(const PGresult *res)
Definition: fe-exec.c:2596
FILE * cur_cmd_source
Definition: settings.h:102
sigjmp_buf sigint_interrupt_jmp
Definition: common.c:232
#define COPYBUFSIZ
Definition: copy.c:511
static char * buf
Definition: pg_test_fsync.c:65
void psql_error(const char *fmt,...)
Definition: common.c:177
void PQclear(PGresult *res)
Definition: fe-exec.c:650
#define NULL
Definition: c.h:226
uint64 lineno
Definition: settings.h:108
int PQprotocolVersion(const PGconn *conn)
Definition: fe-connect.c:5940
#define _(x)
Definition: elog.c:84
PGresult * PQgetResult(PGconn *conn)
Definition: fe-exec.c:1702
uint64 stmt_lineno
Definition: settings.h:109
bool handleCopyOut ( PGconn conn,
FILE *  copystream,
PGresult **  res 
)

Definition at line 435 of file copy.c.

References buf, PGRES_COMMAND_OK, PQerrorMessage(), PQfreemem(), PQgetCopyData(), PQgetResult(), PQresultStatus(), psql_error(), and strerror().

Referenced by ProcessResult().

436 {
437  bool OK = true;
438  char *buf;
439  int ret;
440 
441  for (;;)
442  {
443  ret = PQgetCopyData(conn, &buf, 0);
444 
445  if (ret < 0)
446  break; /* done or server/connection error */
447 
448  if (buf)
449  {
450  if (OK && fwrite(buf, 1, ret, copystream) != ret)
451  {
452  psql_error("could not write COPY data: %s\n",
453  strerror(errno));
454  /* complain only once, keep reading data from server */
455  OK = false;
456  }
457  PQfreemem(buf);
458  }
459  }
460 
461  if (OK && fflush(copystream))
462  {
463  psql_error("could not write COPY data: %s\n",
464  strerror(errno));
465  OK = false;
466  }
467 
468  if (ret == -2)
469  {
470  psql_error("COPY data transfer failed: %s", PQerrorMessage(conn));
471  OK = false;
472  }
473 
474  /*
475  * Check command status and return to normal libpq state.
476  *
477  * If for some reason libpq is still reporting PGRES_COPY_OUT state, we
478  * would like to forcibly exit that state, since our caller would be
479  * unable to distinguish that situation from reaching the next COPY in a
480  * command string that happened to contain two consecutive COPY TO STDOUT
481  * commands. However, libpq provides no API for doing that, and in
482  * principle it's a libpq bug anyway if PQgetCopyData() returns -1 or -2
483  * but hasn't exited COPY_OUT state internally. So we ignore the
484  * possibility here.
485  */
486  *res = PQgetResult(conn);
487  if (PQresultStatus(*res) != PGRES_COMMAND_OK)
488  {
489  psql_error("%s", PQerrorMessage(conn));
490  OK = false;
491  }
492 
493  return OK;
494 }
char * PQerrorMessage(const PGconn *conn)
Definition: fe-connect.c:5960
ExecStatusType PQresultStatus(const PGresult *res)
Definition: fe-exec.c:2596
int PQgetCopyData(PGconn *conn, char **buffer, int async)
Definition: fe-exec.c:2377
static char * buf
Definition: pg_test_fsync.c:65
void psql_error(const char *fmt,...)
Definition: common.c:177
const char * strerror(int errnum)
Definition: strerror.c:19
void PQfreemem(void *ptr)
Definition: fe-exec.c:3200
PGresult * PQgetResult(PGconn *conn)
Definition: fe-exec.c:1702
static struct copy_options* parse_slash_copy ( const char *  args)
static

Definition at line 90 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().

91 {
92  struct copy_options *result;
93  char *token;
94  const char *whitespace = " \t\n\r";
95  char nonstd_backslash = standard_strings() ? 0 : '\\';
96 
97  if (!args)
98  {
99  psql_error("\\copy: arguments required\n");
100  return NULL;
101  }
102 
103  result = pg_malloc0(sizeof(struct copy_options));
104 
105  result->before_tofrom = pg_strdup(""); /* initialize for appending */
106 
107  token = strtokx(args, whitespace, ".,()", "\"",
108  0, false, false, pset.encoding);
109  if (!token)
110  goto error;
111 
112  /* The following can be removed when we drop 7.3 syntax support */
113  if (pg_strcasecmp(token, "binary") == 0)
114  {
115  xstrcat(&result->before_tofrom, token);
116  token = strtokx(NULL, whitespace, ".,()", "\"",
117  0, false, false, pset.encoding);
118  if (!token)
119  goto error;
120  }
121 
122  /* Handle COPY (query) case */
123  if (token[0] == '(')
124  {
125  int parens = 1;
126 
127  while (parens > 0)
128  {
129  xstrcat(&result->before_tofrom, " ");
130  xstrcat(&result->before_tofrom, token);
131  token = strtokx(NULL, whitespace, "()", "\"'",
132  nonstd_backslash, true, false, pset.encoding);
133  if (!token)
134  goto error;
135  if (token[0] == '(')
136  parens++;
137  else if (token[0] == ')')
138  parens--;
139  }
140  }
141 
142  xstrcat(&result->before_tofrom, " ");
143  xstrcat(&result->before_tofrom, token);
144  token = strtokx(NULL, whitespace, ".,()", "\"",
145  0, false, false, pset.encoding);
146  if (!token)
147  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  if (token[0] == '.')
154  {
155  /* handle schema . table */
156  xstrcat(&result->before_tofrom, token);
157  token = strtokx(NULL, whitespace, ".,()", "\"",
158  0, false, false, pset.encoding);
159  if (!token)
160  goto error;
161  xstrcat(&result->before_tofrom, token);
162  token = strtokx(NULL, whitespace, ".,()", "\"",
163  0, false, false, pset.encoding);
164  if (!token)
165  goto error;
166  }
167 
168  if (token[0] == '(')
169  {
170  /* handle parenthesized column list */
171  for (;;)
172  {
173  xstrcat(&result->before_tofrom, " ");
174  xstrcat(&result->before_tofrom, token);
175  token = strtokx(NULL, whitespace, "()", "\"",
176  0, false, false, pset.encoding);
177  if (!token)
178  goto error;
179  if (token[0] == ')')
180  break;
181  }
182  xstrcat(&result->before_tofrom, " ");
183  xstrcat(&result->before_tofrom, token);
184  token = strtokx(NULL, whitespace, ".,()", "\"",
185  0, false, false, pset.encoding);
186  if (!token)
187  goto error;
188  }
189 
190  if (pg_strcasecmp(token, "from") == 0)
191  result->from = true;
192  else if (pg_strcasecmp(token, "to") == 0)
193  result->from = false;
194  else
195  goto error;
196 
197  /* { 'filename' | PROGRAM 'command' | STDIN | STDOUT | PSTDIN | PSTDOUT } */
198  token = strtokx(NULL, whitespace, ";", "'",
199  0, false, false, pset.encoding);
200  if (!token)
201  goto error;
202 
203  if (pg_strcasecmp(token, "program") == 0)
204  {
205  int toklen;
206 
207  token = strtokx(NULL, whitespace, ";", "'",
208  0, false, false, pset.encoding);
209  if (!token)
210  goto error;
211 
212  /*
213  * The shell command must be quoted. This isn't fool-proof, but
214  * catches most quoting errors.
215  */
216  toklen = strlen(token);
217  if (token[0] != '\'' || toklen < 2 || token[toklen - 1] != '\'')
218  goto error;
219 
220  strip_quotes(token, '\'', 0, pset.encoding);
221 
222  result->program = true;
223  result->file = pg_strdup(token);
224  }
225  else if (pg_strcasecmp(token, "stdin") == 0 ||
226  pg_strcasecmp(token, "stdout") == 0)
227  {
228  result->file = NULL;
229  }
230  else if (pg_strcasecmp(token, "pstdin") == 0 ||
231  pg_strcasecmp(token, "pstdout") == 0)
232  {
233  result->psql_inout = true;
234  result->file = NULL;
235  }
236  else
237  {
238  /* filename can be optionally quoted */
239  strip_quotes(token, '\'', 0, pset.encoding);
240  result->file = pg_strdup(token);
241  expand_tilde(&result->file);
242  }
243 
244  /* Collect the rest of the line (COPY options) */
245  token = strtokx(NULL, "", NULL, NULL,
246  0, false, false, pset.encoding);
247  if (token)
248  result->after_tofrom = pg_strdup(token);
249 
250  return result;
251 
252 error:
253  if (token)
254  psql_error("\\copy: parse error at \"%s\"\n", token);
255  else
256  psql_error("\\copy: parse error at end of line\n");
257  free_copy_options(result);
258 
259  return NULL;
260 }
PsqlSettings pset
Definition: startup.c:37
static void error(void)
Definition: sql-dyntest.c:147
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
static void free_copy_options(struct copy_options *ptr)
Definition: copy.c:66
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
char * before_tofrom
Definition: copy.c:56
bool psql_inout
Definition: copy.c:60
void * pg_malloc0(size_t size)
Definition: fe_memutils.c:53
void expand_tilde(char **filename)
Definition: common.c:2053
bool program
Definition: copy.c:59
static void xstrcat(char **var, const char *more)
Definition: copy.c:79
char * pg_strdup(const char *in)
Definition: fe_memutils.c:85
bool from
Definition: copy.c:61
void psql_error(const char *fmt,...)
Definition: common.c:177
void strip_quotes(char *source, char quote, char escape, int encoding)
Definition: stringutils.c:240
bool standard_strings(void)
Definition: common.c:2008
char * after_tofrom
Definition: copy.c:57
#define NULL
Definition: c.h:226
int encoding
Definition: settings.h:83
char * file
Definition: copy.c:58
static void xstrcat ( char **  var,
const char *  more 
)
static

Definition at line 79 of file copy.c.

References free, and psprintf().

Referenced by parse_slash_copy().

80 {
81  char *newvar;
82 
83  newvar = psprintf("%s%s", *var, more);
84  free(*var);
85  *var = newvar;
86 }
char * psprintf(const char *fmt,...)
Definition: psprintf.c:46
#define free(a)
Definition: header.h:60