mirror of
https://github.com/postgres/postgres.git
synced 2025-08-06 18:42:54 +03:00
Add support for piping COPY to/from an external program.
This includes backend "COPY TO/FROM PROGRAM '...'" syntax, and corresponding psql \copy syntax. Like with reading/writing files, the backend version is superuser-only, and in the psql version, the program is run in the client. In the passing, the psql \copy STDIN/STDOUT syntax is subtly changed: if you the stdin/stdout is quoted, it's now interpreted as a filename. For example, "\copy foo from 'stdin'" now reads from a file called 'stdin', not from standard input. Before this, there was no way to specify a filename called stdin, stdout, pstdin or pstdout. This creates a new function in pgport, wait_result_to_str(), which can be used to convert the exit status of a process, as returned by wait(3), to a human-readable string. Etsuro Fujita, reviewed by Amit Kapila.
This commit is contained in:
@@ -39,13 +39,13 @@
|
||||
* for a long time, like relation files. It is the caller's responsibility
|
||||
* to close them, there is no automatic mechanism in fd.c for that.
|
||||
*
|
||||
* AllocateFile, AllocateDir and OpenTransientFile are wrappers around
|
||||
* fopen(3), opendir(3), and open(2), respectively. They behave like the
|
||||
* corresponding native functions, except that the handle is registered with
|
||||
* the current subtransaction, and will be automatically closed at abort.
|
||||
* These are intended for short operations like reading a configuration file,
|
||||
* and there is a fixed limit on the number of files that can be opened using
|
||||
* these functions at any one time.
|
||||
* AllocateFile, AllocateDir, OpenPipeStream and OpenTransientFile are
|
||||
* wrappers around fopen(3), opendir(3), popen(3) and open(2), respectively.
|
||||
* They behave like the corresponding native functions, except that the handle
|
||||
* is registered with the current subtransaction, and will be automatically
|
||||
* closed at abort. These are intended for short operations like reading a
|
||||
* configuration file, and there is a fixed limit on the number of files that
|
||||
* can be opened using these functions at any one time.
|
||||
*
|
||||
* Finally, BasicOpenFile is just a thin wrapper around open() that can
|
||||
* release file descriptors in use by the virtual file descriptors if
|
||||
@@ -202,6 +202,7 @@ static uint64 temporary_files_size = 0;
|
||||
typedef enum
|
||||
{
|
||||
AllocateDescFile,
|
||||
AllocateDescPipe,
|
||||
AllocateDescDir,
|
||||
AllocateDescRawFD
|
||||
} AllocateDescKind;
|
||||
@@ -1585,6 +1586,61 @@ OpenTransientFile(FileName fileName, int fileFlags, int fileMode)
|
||||
return -1; /* failure */
|
||||
}
|
||||
|
||||
/*
|
||||
* Routines that want to initiate a pipe stream should use OpenPipeStream
|
||||
* rather than plain popen(). This lets fd.c deal with freeing FDs if
|
||||
* necessary. When done, call ClosePipeStream rather than pclose.
|
||||
*/
|
||||
FILE *
|
||||
OpenPipeStream(const char *command, const char *mode)
|
||||
{
|
||||
FILE *file;
|
||||
|
||||
DO_DB(elog(LOG, "OpenPipeStream: Allocated %d (%s)",
|
||||
numAllocatedDescs, command));
|
||||
|
||||
/*
|
||||
* The test against MAX_ALLOCATED_DESCS prevents us from overflowing
|
||||
* allocatedFiles[]; the test against max_safe_fds prevents AllocateFile
|
||||
* from hogging every one of the available FDs, which'd lead to infinite
|
||||
* looping.
|
||||
*/
|
||||
if (numAllocatedDescs >= MAX_ALLOCATED_DESCS ||
|
||||
numAllocatedDescs >= max_safe_fds - 1)
|
||||
elog(ERROR, "exceeded MAX_ALLOCATED_DESCS while trying to execute command \"%s\"",
|
||||
command);
|
||||
|
||||
TryAgain:
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
errno = 0;
|
||||
if ((file = popen(command, mode)) != NULL)
|
||||
{
|
||||
AllocateDesc *desc = &allocatedDescs[numAllocatedDescs];
|
||||
|
||||
desc->kind = AllocateDescPipe;
|
||||
desc->desc.file = file;
|
||||
desc->create_subid = GetCurrentSubTransactionId();
|
||||
numAllocatedDescs++;
|
||||
return desc->desc.file;
|
||||
}
|
||||
|
||||
if (errno == EMFILE || errno == ENFILE)
|
||||
{
|
||||
int save_errno = errno;
|
||||
|
||||
ereport(LOG,
|
||||
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
|
||||
errmsg("out of file descriptors: %m; release and retry")));
|
||||
errno = 0;
|
||||
if (ReleaseLruFile())
|
||||
goto TryAgain;
|
||||
errno = save_errno;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free an AllocateDesc of any type.
|
||||
*
|
||||
@@ -1601,6 +1657,9 @@ FreeDesc(AllocateDesc *desc)
|
||||
case AllocateDescFile:
|
||||
result = fclose(desc->desc.file);
|
||||
break;
|
||||
case AllocateDescPipe:
|
||||
result = pclose(desc->desc.file);
|
||||
break;
|
||||
case AllocateDescDir:
|
||||
result = closedir(desc->desc.dir);
|
||||
break;
|
||||
@@ -1814,6 +1873,31 @@ FreeDir(DIR *dir)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Close a pipe stream returned by OpenPipeStream.
|
||||
*/
|
||||
int
|
||||
ClosePipeStream(FILE *file)
|
||||
{
|
||||
int i;
|
||||
|
||||
DO_DB(elog(LOG, "ClosePipeStream: Allocated %d", numAllocatedDescs));
|
||||
|
||||
/* Remove file from list of allocated files, if it's present */
|
||||
for (i = numAllocatedDescs; --i >= 0;)
|
||||
{
|
||||
AllocateDesc *desc = &allocatedDescs[i];
|
||||
|
||||
if (desc->kind == AllocateDescPipe && desc->desc.file == file)
|
||||
return FreeDesc(desc);
|
||||
}
|
||||
|
||||
/* Only get here if someone passes us a file not in allocatedDescs */
|
||||
elog(WARNING, "file passed to ClosePipeStream was not obtained from OpenPipeStream");
|
||||
|
||||
return pclose(file);
|
||||
}
|
||||
|
||||
/*
|
||||
* closeAllVfds
|
||||
*
|
||||
|
Reference in New Issue
Block a user