From 5627c18f5a26ad8314f0590ce8ccf3b4093efb74 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 5 Mar 2018 13:42:29 -0300
Subject: [PATCH v5] Allow parallel dump to go to /dev/null

This is useful to quickly determine whether a dump would finish
without errors.

Author: Michael Banck
Discussion: https://postgr.es/m/20180201132446.GA13784@nighthawk.caipicrew.dd-dns.de
---
 src/bin/pg_dump/compress_io.c         |  8 +++++--
 src/bin/pg_dump/pg_backup_directory.c | 43 ++++++++++++++++++++++++++++-------
 2 files changed, 41 insertions(+), 10 deletions(-)

diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index a96da15dc1..a83b1e2ef5 100644
--- a/src/bin/pg_dump/compress_io.c
+++ b/src/bin/pg_dump/compress_io.c
@@ -496,7 +496,8 @@ cfopen_read(const char *path, const char *mode)
  *
  * If 'compression' is non-zero, a gzip compressed stream is opened, and
  * 'compression' indicates the compression level used. The ".gz" suffix
- * is automatically added to 'path' in that case.
+ * is automatically added to 'path' in that case (unless it's the null
+ * device).
  *
  * On failure, return NULL with an error code in errno.
  */
@@ -512,7 +513,10 @@ cfopen_write(const char *path, const char *mode, int compression)
 #ifdef HAVE_LIBZ
 		char	   *fname;
 
-		fname = psprintf("%s.gz", path);
+		if (strcmp(path, DEVNULL) != 0)
+			fname = psprintf("%s.gz", path);
+		else
+			fname = pg_strdup(path);
 		fp = cfopen(fname, mode, compression);
 		free_keep_errno(fname);
 #else
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 4aabb40f59..3b77023d77 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -54,6 +54,7 @@ typedef struct
 
 	cfp		   *blobsTocFH;		/* file handle for blobs.toc */
 	ParallelState *pstate;		/* for parallel backup / restore */
+	bool		discard;		/* target is DEVNULL */
 } lclContext;
 
 typedef struct
@@ -158,6 +159,8 @@ InitArchiveFmt_Directory(ArchiveHandle *AH)
 
 	ctx->directory = AH->fSpec;
 
+	ctx->discard = strcmp(ctx->directory, DEVNULL) == 0;
+
 	if (AH->mode == archModeWrite)
 	{
 		struct stat st;
@@ -192,9 +195,15 @@ InitArchiveFmt_Directory(ArchiveHandle *AH)
 			}
 		}
 
-		if (!is_empty && mkdir(ctx->directory, 0700) < 0)
-			exit_horribly(modulename, "could not create directory \"%s\": %s\n",
-						  ctx->directory, strerror(errno));
+		/*
+		 * Create the output directory, unless it exists already and is empty.
+		 * If we are discarding output to the null device, we must not
+		 * create it.
+		 */
+		if (!is_empty && !ctx->discard)
+			if (mkdir(ctx->directory, 0700) < 0)
+				exit_horribly(modulename, "could not create directory \"%s\": %s\n",
+							  ctx->directory, strerror(errno));
 	}
 	else
 	{							/* Read Mode */
@@ -602,8 +611,10 @@ _CloseArchive(ArchiveHandle *AH)
 		/*
 		 * In directory mode, there is no need to sync all the entries
 		 * individually. Just recurse once through all the files generated.
+		 * If we are discarding output to the null device, it does not
+		 * need syncing and would emit a warning if we did.
 		 */
-		if (AH->dosync)
+		if (AH->dosync && !ctx->discard)
 			fsync_dir_recurse(ctx->directory, progname);
 	}
 	AH->FH = NULL;
@@ -659,7 +670,14 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 	lclContext *ctx = (lclContext *) AH->formatData;
 	char		fname[MAXPGPATH];
 
-	snprintf(fname, MAXPGPATH, "%s/blob_%u.dat", ctx->directory, oid);
+	/*
+	 * If we are discarding output to the null device, just use that as
+	 * fname.
+	 */
+	if (ctx->discard)
+		snprintf(fname, MAXPGPATH, DEVNULL);
+	else
+		snprintf(fname, MAXPGPATH, "%s/blob_%u.dat", ctx->directory, oid);
 
 	ctx->dataFH = cfopen_write(fname, PG_BINARY_W, AH->compression);
 
@@ -721,9 +739,18 @@ setFilePath(ArchiveHandle *AH, char *buf, const char *relativeFilename)
 	if (strlen(dname) + 1 + strlen(relativeFilename) + 1 > MAXPGPATH)
 		exit_horribly(modulename, "file name too long: \"%s\"\n", dname);
 
-	strcpy(buf, dname);
-	strcat(buf, "/");
-	strcat(buf, relativeFilename);
+	/*
+	 * If we are discarding output to the null device, we cannot set a file
+	 * path and just set the buffer to the null device.
+	 */
+	if (ctx->discard)
+		strcpy(buf, DEVNULL);
+	else
+	{
+		strcpy(buf, dname);
+		strcat(buf, "/");
+		strcat(buf, relativeFilename);
+	}
 }
 
 /*
-- 
2.11.0

