PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
copy.c File Reference
#include "postgres_fe.h"
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>
#include "common.h"
#include "common/logging.h"
#include "copy.h"
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "prompt.h"
#include "settings.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

◆ COPYBUFSIZ

#define COPYBUFSIZ   8192

Definition at line 508 of file copy.c.

Function Documentation

◆ do_copy()

bool do_copy ( const char *  args)

Definition at line 268 of file copy.c.

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)
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}
void SetShellResultVariables(int wait_result)
Definition: common.c:516
bool SendQuery(const char *query)
Definition: common.c:1118
static struct copy_options * parse_slash_copy(const char *args)
Definition: copy.c:89
static void free_copy_options(struct copy_options *ptr)
Definition: copy.c:65
#define PG_BINARY_R
Definition: c.h:1246
#define PG_BINARY_W
Definition: c.h:1247
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
static bool success
Definition: initdb.c:187
#define pg_log_error(...)
Definition: logging.h:106
static char ** options
void canonicalize_path_enc(char *path, int encoding)
Definition: path.c:344
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
PsqlSettings pset
Definition: startup.c:32
FILE * copyStream
Definition: settings.h:108
FILE * queryFout
Definition: settings.h:105
FILE * cur_cmd_source
Definition: settings.h:138
char * wait_result_to_str(int exitstatus)
Definition: wait_error.c:33
#define S_ISDIR(m)
Definition: win32_port.h:315
#define fstat
Definition: win32_port.h:273

References appendPQExpBufferStr(), generate_unaccent_rules::args, canonicalize_path_enc(), _psqlSettings::copyStream, _psqlSettings::cur_cmd_source, PQExpBufferData::data, disable_sigpipe_trap(), _psqlSettings::encoding, free, free_copy_options(), fstat, initPQExpBuffer(), options, parse_slash_copy(), PG_BINARY_R, PG_BINARY_W, pg_log_error, printfPQExpBuffer(), pset, _psqlSettings::queryFout, restore_sigpipe_trap(), S_ISDIR, SendQuery(), SetShellResultVariables(), stat::st_mode, generate_unaccent_rules::stdout, success, termPQExpBuffer(), and wait_result_to_str().

Referenced by exec_command_copy().

◆ free_copy_options()

static void free_copy_options ( struct copy_options ptr)
static

Definition at line 65 of file copy.c.

66{
67 if (!ptr)
68 return;
69 free(ptr->before_tofrom);
70 free(ptr->after_tofrom);
71 free(ptr->file);
72 free(ptr);
73}
char * file
Definition: copy.c:57
char * before_tofrom
Definition: copy.c:55
char * after_tofrom
Definition: copy.c:56

References copy_options::after_tofrom, copy_options::before_tofrom, copy_options::file, and free.

Referenced by do_copy(), and parse_slash_copy().

◆ handleCopyIn()

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

Definition at line 511 of file copy.c.

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
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
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 /*
624 * When at the beginning of the line and the data is
625 * inlined, check for EOF marker. If the marker is found,
626 * we must stop at this point. If not, the \. line can be
627 * sent to the server, and we let it decide whether it's
628 * an EOF or not depending on the format: in TEXT mode, \.
629 * will be interpreted as an EOF, in CSV, it will not.
630 */
631 if (at_line_begin && copystream == pset.cur_cmd_source)
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 * Remove the EOF marker from the data sent. In
640 * CSV mode, the EOF marker must be removed,
641 * otherwise it would be interpreted by the server
642 * as valid data.
643 */
644 *fgresult = '\0';
645 buflen -= linelen;
646 }
647 }
648
649 if (copystream == pset.cur_cmd_source)
650 {
651 pset.lineno++;
653 }
654 at_line_begin = true;
655 }
656 else
657 at_line_begin = false;
658 }
659
660 /*
661 * If the buffer is full, or we've reached the EOF, flush it.
662 *
663 * Make sure there's always space for four more bytes in the
664 * buffer, plus a NUL terminator. That way, an EOF marker is
665 * never split across two fgets() calls, which simplifies the
666 * logic.
667 */
668 if (buflen >= COPYBUFSIZ - 5 || (copydone && buflen > 0))
669 {
670 if (PQputCopyData(conn, buf, buflen) <= 0)
671 {
672 OK = false;
673 break;
674 }
675
676 buflen = 0;
677 }
678 }
679 }
680
681 /* Check for read error */
682 if (ferror(copystream))
683 OK = false;
684
685 /*
686 * Terminate data transfer. We can't send an error message if we're using
687 * protocol version 2. (libpq no longer supports protocol version 2, but
688 * keep the version checks just in case you're using a pre-v14 libpq.so at
689 * runtime)
690 */
691 if (PQputCopyEnd(conn,
692 (OK || PQprotocolVersion(conn) < 3) ? NULL :
693 _("aborted because of read failure")) <= 0)
694 OK = false;
695
696copyin_cleanup:
697
698 /*
699 * Clear the EOF flag on the stream, in case copying ended due to an EOF
700 * signal. This allows an interactive TTY session to perform another COPY
701 * FROM STDIN later. (In non-STDIN cases, we're about to close the file
702 * anyway, so it doesn't matter.) Although we don't ever test the flag
703 * with feof(), some fread() implementations won't read more data if it's
704 * set. This also clears the error flag, but we already checked that.
705 */
706 clearerr(copystream);
707
708 /*
709 * Check command status and return to normal libpq state.
710 *
711 * We do not want to return with the status still PGRES_COPY_IN: our
712 * caller would be unable to distinguish that situation from reaching the
713 * next COPY in a command string that happened to contain two consecutive
714 * COPY FROM STDIN commands. We keep trying PQputCopyEnd() in the hope
715 * it'll work eventually. (What's actually likely to happen is that in
716 * attempting to flush the data, libpq will eventually realize that the
717 * connection is lost. But that's fine; it will get us out of COPY_IN
718 * state, which is what we need.)
719 */
720 while (*res = PQgetResult(conn), PQresultStatus(*res) == PGRES_COPY_IN)
721 {
722 OK = false;
723 PQclear(*res);
724 /* We can't send an error message if we're using protocol version 2 */
726 (PQprotocolVersion(conn) < 3) ? NULL :
727 _("trying to exit copy mode"));
728 }
729 if (PQresultStatus(*res) != PGRES_COMMAND_OK)
730 {
732 OK = false;
733 }
734
735 return OK;
736}
volatile sig_atomic_t sigint_interrupt_enabled
Definition: common.c:304
sigjmp_buf sigint_interrupt_jmp
Definition: common.c:306
#define COPYBUFSIZ
Definition: copy.c:508
#define _(x)
Definition: elog.c:90
int PQprotocolVersion(const PGconn *conn)
Definition: fe-connect.c:7523
char * PQerrorMessage(const PGconn *conn)
Definition: fe-connect.c:7553
PGresult * PQgetResult(PGconn *conn)
Definition: fe-exec.c:2062
ExecStatusType PQresultStatus(const PGresult *res)
Definition: fe-exec.c:3411
void PQclear(PGresult *res)
Definition: fe-exec.c:721
int PQputCopyEnd(PGconn *conn, const char *errormsg)
Definition: fe-exec.c:2749
int PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
Definition: fe-exec.c:2695
@ PGRES_COPY_IN
Definition: libpq-fe.h:132
@ PGRES_COMMAND_OK
Definition: libpq-fe.h:125
#define pg_log_info(...)
Definition: logging.h:124
static char * buf
Definition: pg_test_fsync.c:72
char * get_prompt(promptStatus_t status, ConditionalStack cstack)
Definition: prompt.c:68
@ PROMPT_COPY
Definition: psqlscan.h:48
PGconn * conn
Definition: streamutil.c:52
uint64 lineno
Definition: settings.h:144
uint64 stmt_lineno
Definition: settings.h:145

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

Referenced by HandleCopyResult().

◆ handleCopyOut()

bool handleCopyOut ( PGconn conn,
FILE *  copystream,
PGresult **  res 
)

Definition at line 434 of file copy.c.

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);
484 if (PQresultStatus(*res) != PGRES_COMMAND_OK)
485 {
487 OK = false;
488 }
489
490 return OK;
491}
void PQfreemem(void *ptr)
Definition: fe-exec.c:4032
int PQgetCopyData(PGconn *conn, char **buffer, int async)
Definition: fe-exec.c:2816

References buf, conn, pg_log_error, pg_log_info, PGRES_COMMAND_OK, PQerrorMessage(), PQfreemem(), PQgetCopyData(), PQgetResult(), and PQresultStatus().

Referenced by HandleCopyResult().

◆ parse_slash_copy()

static struct copy_options * parse_slash_copy ( const char *  args)
static

Definition at line 89 of file copy.c.

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
251error:
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}
void expand_tilde(char **filename)
Definition: common.c:2497
bool standard_strings(void)
Definition: common.c:2456
static void xstrcat(char **var, const char *more)
Definition: copy.c:78
char * pg_strdup(const char *in)
Definition: fe_memutils.c:85
void * pg_malloc0(size_t size)
Definition: fe_memutils.c:53
#define token
Definition: indent_globs.h:126
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
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
void strip_quotes(char *source, char quote, char escape, int encoding)
Definition: stringutils.c:240
bool program
Definition: copy.c:58
bool from
Definition: copy.c:60
bool psql_inout
Definition: copy.c:59

References copy_options::after_tofrom, generate_unaccent_rules::args, copy_options::before_tofrom, _psqlSettings::encoding, error(), expand_tilde(), copy_options::file, free_copy_options(), copy_options::from, pg_log_error, pg_malloc0(), pg_strcasecmp(), pg_strdup(), copy_options::program, pset, copy_options::psql_inout, standard_strings(), strip_quotes(), strtokx(), token, and xstrcat().

Referenced by do_copy().

◆ xstrcat()

static void xstrcat ( char **  var,
const char *  more 
)
static

Definition at line 78 of file copy.c.

79{
80 char *newvar;
81
82 newvar = psprintf("%s%s", *var, more);
83 free(*var);
84 *var = newvar;
85}
char * psprintf(const char *fmt,...)
Definition: psprintf.c:43

References free, and psprintf().

Referenced by parse_slash_copy().