agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
Re: pg_ls_tmpdir to show directories and shared filesets
70+ messages / 12 participants
[nested] [flat]

* Re: pg_ls_tmpdir to show directories and shared filesets
@ 2020-03-05 16:18 Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-05 16:18 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: David Steele <david@pgmasters.net>; Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Tue, Mar 03, 2020 at 05:23:13PM -0300, Alvaro Herrera wrote:
> On 2020-Mar-03, Justin Pryzby wrote:
> 
> > But I don't think it makes sense to go through more implementation/review
> > cycles without some agreement from a larger group regarding the
> > desired/intended interface.  Should there be a column for "parent dir" ?  Or a
> > column for "is_dir" ?  Should dirs be shown at all, or only files ?
> 
> IMO: is_dir should be there (and subdirs should be listed), but
> parent_dir should not appear.  Also, the "path" should show the complete
> pathname, including containing dirs, starting from whatever the "root"
> is for the operation.
> 
> So for the example in the initial email, it would look like
> 
> path					isdir
> pgsql_tmp11025.0.sharedfileset/		t
> pgsql_tmp11025.0.sharedfileset/0.0	f
> pgsql_tmp11025.0.sharedfileset/1.0	f
> 
> plus additional columns, same as pg_ls_waldir et al.
> 
> I'd rather not have the code assume that there's a single level of
> subdirs, or assuming that an entry in the subdir cannot itself be a dir;
> that might end up hiding files for no good reason.
> 

Thanks for your input, see attached.

I'm not sure if prefer the 0002 patch alone (which recurses into dirs all at
once during the initial call), or 0002+3+4, which incrementally reads the dirs
on each call (but requires keeping dirs opened).

> I don't understand what purpose is served by having pg_ls_waldir() hide
> directories.

We could talk about whether the other functions should show dirs, if it's worth
breaking their return type.  Or if they should show hidden or special files,
which doesn't require breaking the return.  But until then I am to leave the
behavior alone.

-- 
Justin

Attachments:

  [text/x-diff] v6-0001-BUG-in-errmsg.patch (948B, ../../20200305161838.GJ684@telsasoft.com/2-v6-0001-BUG-in-errmsg.patch)
  download | inline diff:
From a5b9a03445d1c768662cafebd8ab3bd7a62890aa Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 27 Dec 2019 23:34:14 -0600
Subject: [PATCH v6 1/4] BUG: in errmsg

Note there's two changes here.
Should backpatch to v12, where pg_ls_tmpdir was added.
---
 src/backend/utils/adt/genfile.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3741b87486..897b11a77d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -590,7 +590,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 		if (stat(path, &attrib) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
-					 errmsg("could not stat directory \"%s\": %m", dir)));
+					 errmsg("could not stat file \"%s\": %m", path)));
 
 		/* Ignore anything but regular files */
 		if (!S_ISREG(attrib.st_mode))
-- 
2.17.0

  [text/x-diff] v6-0002-pg_ls_tmpdir-to-show-directories.patch (10.7K, ../../20200305161838.GJ684@telsasoft.com/3-v6-0002-pg_ls_tmpdir-to-show-directories.patch)
  download | inline diff:
From 2437276b9c7525981d4a70b804c81021b2f5fa1f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 14 Dec 2019 16:22:15 -0600
Subject: [PATCH v6 2/4] pg_ls_tmpdir to show directories

See also 9cd92d1a33699f86aa53d44ab04cc3eb50c18d11
---
 doc/src/sgml/func.sgml           |  13 +--
 src/backend/utils/adt/genfile.c  | 133 +++++++++++++++++++++++--------
 src/include/catalog/catversion.h |   2 +-
 src/include/catalog/pg_proc.dat  |   8 +-
 4 files changed, 114 insertions(+), 42 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..35abff16c9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21382,8 +21382,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
+        For the temporary directory for <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and boolean
+        indicating if it is a directory.  Directories are shown recursively.  If
         <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is used.  Access is granted
         to members of the <literal>pg_monitor</literal> role and may be
@@ -21479,9 +21480,11 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and boolean indicating if it is a
+    directory.  Directories are used for temporary files used by parallel
+    processes, and are shown recursively.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 897b11a77d..c5148f547b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -40,6 +40,11 @@ typedef struct
 	char	   *location;
 	DIR		   *dirdesc;
 	bool		include_dot_dirs;
+
+	/* Used in ls_dir_files: */
+	int			npaths;
+	char		**path;
+	struct stat	*stat;
 } directory_fctx;
 
 
@@ -522,12 +527,73 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 	return pg_ls_dir(fcinfo);
 }
 
-/* Generic function to return a directory listing of files */
+/*
+ * Populate fctx with list of pathnames and stat structues.  Generate a full
+ * list of the stat structures all at once - the alternative is to re-stat
+ * everything later on, which then requires somehow handling cases like the 2nd
+ * stat fails, or the 2nd stat returns a dir which didn't exist at the time we
+ * originally looked, or existed but wasn't a dir.  stat()ing everything all at
+ * once gets as close as we can to a consistent view of the filesystem, from
+ * which files might be removed or renamed or reordered.
+ */
+static void
+populate_paths(directory_fctx *fctx, bool recurse)
+{
+	struct dirent *de;
+	struct stat	attrib;
+	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+	{
+		char		path[MAXPGPATH];
+
+		/* Skip hidden files */
+		if (de->d_name[0] == '.')
+			continue;
+
+		/* Get the file info */
+		snprintf(path, sizeof(path), "%s/%s", fctx->location, de->d_name);
+		if (stat(path, &attrib) < 0)
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not stat file \"%s\": %m", path)));
+
+		/* Ignore anything but regular files, or dirs, if requested */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			/* Save current dir while recursing */
+			directory_fctx oldfctx = *fctx;
+
+			fctx->location = path;
+			fctx->dirdesc = AllocateDir(path);
+
+			if (recurse)
+				populate_paths(fctx, recurse);
+
+			/* Restore previous dir */
+			FreeDir(fctx->dirdesc);
+			fctx->dirdesc = oldfctx.dirdesc;
+			fctx->location = oldfctx.location;
+		} else if (!S_ISREG(attrib.st_mode))
+			continue;
+
+		/* Reallocate path and stat if npaths is a power of two */
+		if (!(fctx->npaths & (fctx->npaths-1)))
+		{
+			int nalloc = fctx->npaths ? 2 * fctx->npaths : 1;
+			fctx->path = repalloc(fctx->path, nalloc*sizeof(*fctx->path));
+			fctx->stat = repalloc(fctx->stat, nalloc*sizeof(*fctx->stat));
+		}
+
+		fctx->path[fctx->npaths] = pstrdup(path);
+		fctx->stat[fctx->npaths] = attrib;
+		fctx->npaths++;
+	}
+}
+
+/* Generic function to return a directory listing of files (and optionally dirs) */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool dir_ok)
 {
 	FuncCallContext *funcctx;
-	struct dirent *de;
 	directory_fctx *fctx;
 
 	if (SRF_IS_FIRSTCALL())
@@ -540,17 +606,24 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		fctx = palloc(sizeof(directory_fctx));
 
-		tupdesc = CreateTemplateTupleDesc(3);
+		tupdesc = CreateTemplateTupleDesc(dir_ok ? 4:3);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
 						   TEXTOID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
 						   INT8OID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
 						   TIMESTAMPTZOID, -1, 0);
+		if (dir_ok)
+			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+						   BOOLOID, -1, 0);
+
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
 		fctx->location = pstrdup(dir);
 		fctx->dirdesc = AllocateDir(fctx->location);
+		fctx->npaths = 0;
+		fctx->path = palloc(sizeof(*fctx->path));
+		fctx->stat = palloc(sizeof(*fctx->stat));
 
 		if (!fctx->dirdesc)
 		{
@@ -566,6 +639,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 								fctx->location)));
 		}
 
+		populate_paths(fctx, dir_ok);
+		funcctx->max_calls = fctx->npaths;
 		funcctx->user_fctx = fctx;
 		MemoryContextSwitchTo(oldcontext);
 	}
@@ -573,38 +648,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	funcctx = SRF_PERCALL_SETUP();
 	fctx = (directory_fctx *) funcctx->user_fctx;
 
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+	/*
+	 * Having already stat()ed all files, each call now just returns the
+	 * nth element.
+	 */
+	if (funcctx->call_cntr < funcctx->max_calls)
 	{
-		Datum		values[3];
-		bool		nulls[3];
-		char		path[MAXPGPATH * 2];
-		struct stat attrib;
+		struct stat	*stat = &fctx->stat[funcctx->call_cntr];
+		Datum		values[4];
+		bool		nulls[4] = {0};
 		HeapTuple	tuple;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
-			continue;
-
-		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", fctx->location, de->d_name);
-		if (stat(path, &attrib) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", path)));
-
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-		memset(nulls, 0, sizeof(nulls));
+		values[0] = CStringGetTextDatum(fctx->path[funcctx->call_cntr]);
+		values[1] = Int64GetDatum((int64) stat->st_size);
+		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(stat->st_mtime));
+		if (dir_ok)
+			values[3] = BoolGetDatum(S_ISDIR(stat->st_mode));
 
 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
 		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
 	}
 
+	for (int i = 0; i < funcctx->max_calls; ++i)
+		pfree(fctx->path[i]);
+
+	pfree(fctx->path);
+	pfree(fctx->stat);
 	FreeDir(fctx->dirdesc);
 	SRF_RETURN_DONE(funcctx);
 }
@@ -613,14 +682,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, false, false);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, false, false);
 }
 
 /*
@@ -638,7 +707,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path, true, true);
 }
 
 /*
@@ -667,5 +736,5 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, false);
 }
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index d4fe84a037..6e1e2b961b 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	202003031
+#define CATALOG_VERSION_NO	202003032
 
 #endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7b7b..0dbce4ba09 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10739,13 +10739,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 
 # hash partitioning constraint function
-- 
2.17.0

  [text/x-diff] v6-0003-Convert-data-structure-to-use-a-list.patch (5.9K, ../../20200305161838.GJ684@telsasoft.com/4-v6-0003-Convert-data-structure-to-use-a-list.patch)
  download | inline diff:
From 864fa87522c3c0a062c11adfc0421cba92ac6efd Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Wed, 4 Mar 2020 17:38:03 -0600
Subject: [PATCH v6 3/4] Convert data structure to use a list

---
 src/backend/utils/adt/genfile.c | 78 +++++++++++++++++----------------
 1 file changed, 41 insertions(+), 37 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c5148f547b..bc473e79ef 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,8 +37,9 @@
 
 typedef struct
 {
-	char	   *location;
-	DIR		   *dirdesc;
+	/* List of opened dirs */
+	List		*location;
+	List		*dirdesc;
 	bool		include_dot_dirs;
 
 	/* Used in ls_dir_files: */
@@ -474,10 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
 		fctx = palloc(sizeof(directory_fctx));
-		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
+		fctx->location = lappend(NIL, convert_and_check_filename(PG_GETARG_TEXT_PP(0)));
+		fctx->dirdesc = lappend(NIL, AllocateDir(linitial(fctx->location)));
 		fctx->include_dot_dirs = include_dot_dirs;
-		fctx->dirdesc = AllocateDir(fctx->location);
 
 		if (!fctx->dirdesc)
 		{
@@ -490,7 +490,7 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
+								(char*)linitial(fctx->location))));
 		}
 		funcctx->user_fctx = fctx;
 		MemoryContextSwitchTo(oldcontext);
@@ -499,7 +499,7 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 	funcctx = SRF_PERCALL_SETUP();
 	fctx = (directory_fctx *) funcctx->user_fctx;
 
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+	while ((de = ReadDir(linitial(fctx->dirdesc), linitial(fctx->location))) != NULL)
 	{
 		if (!fctx->include_dot_dirs &&
 			(strcmp(de->d_name, ".") == 0 ||
@@ -509,7 +509,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
 	}
 
-	FreeDir(fctx->dirdesc);
+	FreeDir(linitial(fctx->dirdesc));
+	list_free(fctx->dirdesc);
+	list_free_deep(fctx->location);
 
 	SRF_RETURN_DONE(funcctx);
 }
@@ -528,20 +530,25 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Populate fctx with list of pathnames and stat structues.  Generate a full
- * list of the stat structures all at once - the alternative is to re-stat
- * everything later on, which then requires somehow handling cases like the 2nd
- * stat fails, or the 2nd stat returns a dir which didn't exist at the time we
- * originally looked, or existed but wasn't a dir.  stat()ing everything all at
- * once gets as close as we can to a consistent view of the filesystem, from
- * which files might be removed or renamed or reordered.
+ * Populate fctx with list of pathnames and stat structues.
+ *
+ * Generate a full list of the stat structures all at once.  That gets as close
+ * as we can to a consistent view of the filesystem, from which files might be
+ * removed or renamed or reordered.
+ *
+ * recurse is the recursion level into a stack of dirs, or -1 to not decend
+ * into directories.
  */
 static void
-populate_paths(directory_fctx *fctx, bool recurse)
+populate_paths(directory_fctx *fctx, int recurse)
 {
 	struct dirent *de;
 	struct stat	attrib;
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+
+	DIR *dirdesc = llast(fctx->dirdesc);
+	char *location = llast(fctx->location);
+
+	while ((de = ReadDir(dirdesc, location)) != NULL)
 	{
 		char		path[MAXPGPATH];
 
@@ -550,28 +557,23 @@ populate_paths(directory_fctx *fctx, bool recurse)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", fctx->location, de->d_name);
+		snprintf(path, sizeof(path), "%s/%s", location, de->d_name);
 		if (stat(path, &attrib) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
 		/* Ignore anything but regular files, or dirs, if requested */
-		if (S_ISDIR(attrib.st_mode))
+		if (recurse == -1)
+			; /* Do nothing, fall through */
+		else if (S_ISDIR(attrib.st_mode))
 		{
-			/* Save current dir while recursing */
-			directory_fctx oldfctx = *fctx;
-
-			fctx->location = path;
-			fctx->dirdesc = AllocateDir(path);
-
-			if (recurse)
-				populate_paths(fctx, recurse);
-
-			/* Restore previous dir */
-			FreeDir(fctx->dirdesc);
-			fctx->dirdesc = oldfctx.dirdesc;
-			fctx->location = oldfctx.location;
+			/* Reallocate location and dirdesc whenever recursing */
+			fctx->location = lappend(fctx->location, path);
+			fctx->dirdesc = lappend(fctx->dirdesc, AllocateDir(path));
+			populate_paths(fctx, 1+recurse);
+			FreeDir(llast(fctx->dirdesc));
+			list_delete_last(fctx->dirdesc);
 		} else if (!S_ISREG(attrib.st_mode))
 			continue;
 
@@ -619,8 +621,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
-		fctx->location = pstrdup(dir);
-		fctx->dirdesc = AllocateDir(fctx->location);
+		fctx->location = lappend(NIL, pstrdup(dir));
+		fctx->dirdesc = lappend(NIL, AllocateDir(dir));
 		fctx->npaths = 0;
 		fctx->path = palloc(sizeof(*fctx->path));
 		fctx->stat = palloc(sizeof(*fctx->stat));
@@ -636,10 +638,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
+								dir)));
 		}
 
-		populate_paths(fctx, dir_ok);
+		populate_paths(fctx, dir_ok ? 0 : -1);
 		funcctx->max_calls = fctx->npaths;
 		funcctx->user_fctx = fctx;
 		MemoryContextSwitchTo(oldcontext);
@@ -674,7 +676,9 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 
 	pfree(fctx->path);
 	pfree(fctx->stat);
-	FreeDir(fctx->dirdesc);
+	FreeDir(linitial(fctx->dirdesc));
+	list_free(fctx->dirdesc);
+	list_free(fctx->location);
 	SRF_RETURN_DONE(funcctx);
 }
 
-- 
2.17.0

  [text/x-diff] v6-0004-Read-each-dir-incrementally-during-each-SRF-call.patch (6.2K, ../../20200305161838.GJ684@telsasoft.com/5-v6-0004-Read-each-dir-incrementally-during-each-SRF-call.patch)
  download | inline diff:
From 1a0c463d317d0e63885e7442b53875e9a1fd64e3 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Thu, 5 Mar 2020 06:04:35 -0600
Subject: [PATCH v6 4/4] Read each dir incrementally during each SRF call..

This requires a stack and keeping state and opened dirs in the data structure.
It also requires squirreling around with memory contexts.
The SRF implementation seems to preclude use of recursion.
---
 src/backend/utils/adt/genfile.c | 107 ++++++++++++++------------------
 1 file changed, 47 insertions(+), 60 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bc473e79ef..32d5b6f8b9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,15 +37,15 @@
 
 typedef struct
 {
-	/* List of opened dirs */
+	/* Stack of opened dirs */
 	List		*location;
 	List		*dirdesc;
 	bool		include_dot_dirs;
 
 	/* Used in ls_dir_files: */
-	int			npaths;
-	char		**path;
-	struct stat	*stat;
+	bool		dir_ok;
+	char		*path;
+	struct stat	stat;
 } directory_fctx;
 
 
@@ -530,27 +530,37 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Populate fctx with list of pathnames and stat structues.
- *
- * Generate a full list of the stat structures all at once.  That gets as close
- * as we can to a consistent view of the filesystem, from which files might be
- * removed or renamed or reordered.
- *
- * recurse is the recursion level into a stack of dirs, or -1 to not decend
- * into directories.
+ * Update fctx->path and stat with next filename.
+ * Directories are descended into.
+ * The current dir to read from is at fctx[fctx->depth].
  */
-static void
-populate_paths(directory_fctx *fctx, int recurse)
+static int
+populate_paths(directory_fctx *fctx, FuncCallContext *funcctx)
 {
 	struct dirent *de;
-	struct stat	attrib;
-
-	DIR *dirdesc = llast(fctx->dirdesc);
-	char *location = llast(fctx->location);
 
-	while ((de = ReadDir(dirdesc, location)) != NULL)
+	for (;;)
 	{
 		char		path[MAXPGPATH];
+		DIR			*dirdesc = llast(fctx->dirdesc);
+		char		*location = llast(fctx->location);
+
+		Assert(list_length(fctx->dirdesc) == list_length(fctx->location));
+
+		if ((de = ReadDir(dirdesc, location)) == NULL)
+		{
+			/*
+			 * Read to the end of the dir on the top of the stack, now move to
+			 * the next dir.
+			 */
+			if (list_length(fctx->dirdesc) == 1)
+				return 0;
+			FreeDir(llast(fctx->dirdesc));
+			fctx->dirdesc = list_delete_last(fctx->dirdesc);
+			pfree(location);
+			fctx->location = list_delete_last(fctx->location);
+			continue;
+		}
 
 		/* Skip hidden files */
 		if (de->d_name[0] == '.')
@@ -558,36 +568,26 @@ populate_paths(directory_fctx *fctx, int recurse)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", location, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (stat(path, &fctx->stat) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
 		/* Ignore anything but regular files, or dirs, if requested */
-		if (recurse == -1)
+		if (!fctx->dir_ok)
 			; /* Do nothing, fall through */
-		else if (S_ISDIR(attrib.st_mode))
+		else if (S_ISDIR(fctx->stat.st_mode))
 		{
-			/* Reallocate location and dirdesc whenever recursing */
-			fctx->location = lappend(fctx->location, path);
+			/* Reallocate location and dirdesc whenever descending */
+			MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+			fctx->location = lappend(fctx->location, pstrdup(path));
 			fctx->dirdesc = lappend(fctx->dirdesc, AllocateDir(path));
-			populate_paths(fctx, 1+recurse);
-			FreeDir(llast(fctx->dirdesc));
-			list_delete_last(fctx->dirdesc);
-		} else if (!S_ISREG(attrib.st_mode))
+			MemoryContextSwitchTo(oldcontext);
+		} else if (!S_ISREG(fctx->stat.st_mode))
 			continue;
 
-		/* Reallocate path and stat if npaths is a power of two */
-		if (!(fctx->npaths & (fctx->npaths-1)))
-		{
-			int nalloc = fctx->npaths ? 2 * fctx->npaths : 1;
-			fctx->path = repalloc(fctx->path, nalloc*sizeof(*fctx->path));
-			fctx->stat = repalloc(fctx->stat, nalloc*sizeof(*fctx->stat));
-		}
-
-		fctx->path[fctx->npaths] = pstrdup(path);
-		fctx->stat[fctx->npaths] = attrib;
-		fctx->npaths++;
+		fctx->path = pstrdup(path);
+		return 1;
 	}
 }
 
@@ -623,9 +623,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 
 		fctx->location = lappend(NIL, pstrdup(dir));
 		fctx->dirdesc = lappend(NIL, AllocateDir(dir));
-		fctx->npaths = 0;
-		fctx->path = palloc(sizeof(*fctx->path));
-		fctx->stat = palloc(sizeof(*fctx->stat));
+		fctx->dir_ok = dir_ok;
 
 		if (!fctx->dirdesc)
 		{
@@ -641,8 +639,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 								dir)));
 		}
 
-		populate_paths(fctx, dir_ok ? 0 : -1);
-		funcctx->max_calls = fctx->npaths;
 		funcctx->user_fctx = fctx;
 		MemoryContextSwitchTo(oldcontext);
 	}
@@ -650,35 +646,26 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 	funcctx = SRF_PERCALL_SETUP();
 	fctx = (directory_fctx *) funcctx->user_fctx;
 
-	/*
-	 * Having already stat()ed all files, each call now just returns the
-	 * nth element.
-	 */
-	if (funcctx->call_cntr < funcctx->max_calls)
+	if (populate_paths(fctx, funcctx))
 	{
-		struct stat	*stat = &fctx->stat[funcctx->call_cntr];
 		Datum		values[4];
 		bool		nulls[4] = {0};
 		HeapTuple	tuple;
 
-		values[0] = CStringGetTextDatum(fctx->path[funcctx->call_cntr]);
-		values[1] = Int64GetDatum((int64) stat->st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(stat->st_mtime));
+		values[0] = CStringGetTextDatum(fctx->path);
+		values[1] = Int64GetDatum((int64) fctx->stat.st_size);
+		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fctx->stat.st_mtime));
 		if (dir_ok)
-			values[3] = BoolGetDatum(S_ISDIR(stat->st_mode));
+			values[3] = BoolGetDatum(S_ISDIR(fctx->stat.st_mode));
 
 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+		pfree(fctx->path);
 		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
 	}
 
-	for (int i = 0; i < funcctx->max_calls; ++i)
-		pfree(fctx->path[i]);
-
-	pfree(fctx->path);
-	pfree(fctx->stat);
 	FreeDir(linitial(fctx->dirdesc));
 	list_free(fctx->dirdesc);
-	list_free(fctx->location);
+	list_free_deep(fctx->location);
 	SRF_RETURN_DONE(funcctx);
 }
 
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-06 23:35 ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:35 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: David Steele <david@pgmasters.net>; Fabien COELHO <coelho@cri.ensmp.fr>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Thu, Mar 05, 2020 at 10:18:38AM -0600, Justin Pryzby wrote:
> I'm not sure if prefer the 0002 patch alone (which recurses into dirs all at
> once during the initial call), or 0002+3+4, which incrementally reads the dirs
> on each call (but requires keeping dirs opened).

I fixed an issue that leading dirs were being shown which should not have been,
which was easier in the 0004 patch, so squished.  And fixed a bug that
"special" files weren't excluded, and "missing_ok" wasn't effective.

> > I don't understand what purpose is served by having pg_ls_waldir() hide
> > directories.
> 
> We could talk about whether the other functions should show dirs, if it's worth
> breaking their return type.  Or if they should show hidden or special files,
> which doesn't require breaking the return.  But until then I am to leave the
> behavior alone.

I don't see why any of the functions would exclude dirs, but ls_tmpdir deserves
to be fixed since core postgres dynamically creates dirs there.

Also ... I accidentally changed the behavior: master not only doesn't decend
into dirs, it hides them - that was my original complaint.  I propose to *also*
change at least tmpdir and logdir to show dirs, but don't decend.  I left
waldir alone for now.

Since v12 ls_tmpdir and since v10 logdir and waldir exclude dirs, I think we
should backpatch documentation to say so.

ISTM pg_ls_tmpdir and ls_logdir should be called with missing_ok=true, since
they're not created until they're used.

-- 
Justin

Attachments:

  [text/x-diff] v7-0001-BUG-in-errmsg.patch (948B, ../../20200306233507.GN684@telsasoft.com/2-v7-0001-BUG-in-errmsg.patch)
  download | inline diff:
From a5b9a03445d1c768662cafebd8ab3bd7a62890aa Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 27 Dec 2019 23:34:14 -0600
Subject: [PATCH v7 1/6] BUG: in errmsg

Note there's two changes here.
Should backpatch to v12, where pg_ls_tmpdir was added.
---
 src/backend/utils/adt/genfile.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3741b87486..897b11a77d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -590,7 +590,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 		if (stat(path, &attrib) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
-					 errmsg("could not stat directory \"%s\": %m", dir)));
+					 errmsg("could not stat file \"%s\": %m", path)));
 
 		/* Ignore anything but regular files */
 		if (!S_ISREG(attrib.st_mode))
-- 
2.17.0

  [text/x-diff] v7-0002-Document-historic-behavior-about-hiding-directori.patch (1.7K, ../../20200306233507.GN684@telsasoft.com/3-v7-0002-Document-historic-behavior-about-hiding-directori.patch)
  download | inline diff:
From 6ea85ec0a267930320b8454a33bca368a8544a2d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 16:50:07 -0600
Subject: [PATCH v7 2/6] Document historic behavior about hiding directories
 and special files

Should backpatch to v10: tmpdir, waldir and archive_statusdir
---
 doc/src/sgml/func.sgml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..4c0ea5ab3f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21450,6 +21450,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     (mtime) of each file in the log directory. By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21461,6 +21462,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21473,6 +21475,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v7-0003-Document-historic-behavior-about-hiding-directori.patch (903B, ../../20200306233507.GN684@telsasoft.com/4-v7-0003-Document-historic-behavior-about-hiding-directori.patch)
  download | inline diff:
From 5250d637493627f1ff3587bc73dd598bc1ca3ffc Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:12:04 -0600
Subject: [PATCH v7 3/6] Document historic behavior about hiding directories
 and special files

Should backpatch to v12: tmpdir
---
 doc/src/sgml/func.sgml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c0ea5ab3f..fc4d7f0f78 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21489,6 +21489,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v7-0004-pg_ls_tmpdir-to-show-directories.patch (12.2K, ../../20200306233507.GN684@telsasoft.com/5-v7-0004-pg_ls_tmpdir-to-show-directories.patch)
  download | inline diff:
From 8d00a1c80679d9a754a3988786e70c0385b46b30 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 14 Dec 2019 16:22:15 -0600
Subject: [PATCH v7 4/6] pg_ls_tmpdir to show directories

See also 9cd92d1a33699f86aa53d44ab04cc3eb50c18d11
---
 doc/src/sgml/func.sgml          |  14 +--
 src/backend/utils/adt/genfile.c | 167 +++++++++++++++++++++++---------
 src/include/catalog/pg_proc.dat |   8 +-
 3 files changed, 134 insertions(+), 55 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..15f4908924 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21382,8 +21382,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
+        For the temporary directory for <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and boolean
+        indicating if it is a directory.  Directories are shown recursively.  If
         <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is used.  Access is granted
         to members of the <literal>pg_monitor</literal> role and may be
@@ -21482,14 +21483,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and boolean indicating if it is a
+    directory.  Directories are used for temporary files used by parallel
+    processes, and are shown recursively.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 897b11a77d..0207ac6be8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -35,11 +35,23 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+enum dir_action {
+	DIR_HIDE,
+	DIR_ONLY,
+	DIR_DESCEND,
+};
+
 typedef struct
 {
-	char	   *location;
-	DIR		   *dirdesc;
+	/* Stack of opened dirs */
+	List		*location;
+	List		*dirdesc;
 	bool		include_dot_dirs;
+
+	/* Used in ls_dir_files: */
+	enum dir_action dir_action;
+	char		*path;
+	struct stat	stat;
 } directory_fctx;
 
 
@@ -469,10 +481,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
 		fctx = palloc(sizeof(directory_fctx));
-		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
+		fctx->location = lappend(NIL, convert_and_check_filename(PG_GETARG_TEXT_PP(0)));
+		fctx->dirdesc = lappend(NIL, AllocateDir(linitial(fctx->location)));
 		fctx->include_dot_dirs = include_dot_dirs;
-		fctx->dirdesc = AllocateDir(fctx->location);
 
 		if (!fctx->dirdesc)
 		{
@@ -485,7 +496,7 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
+								(char*)linitial(fctx->location))));
 		}
 		funcctx->user_fctx = fctx;
 		MemoryContextSwitchTo(oldcontext);
@@ -494,7 +505,7 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 	funcctx = SRF_PERCALL_SETUP();
 	fctx = (directory_fctx *) funcctx->user_fctx;
 
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+	while ((de = ReadDir(linitial(fctx->dirdesc), linitial(fctx->location))) != NULL)
 	{
 		if (!fctx->include_dot_dirs &&
 			(strcmp(de->d_name, ".") == 0 ||
@@ -504,7 +515,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
 	}
 
-	FreeDir(fctx->dirdesc);
+	FreeDir(linitial(fctx->dirdesc));
+	list_free(fctx->dirdesc);
+	list_free_deep(fctx->location);
 
 	SRF_RETURN_DONE(funcctx);
 }
@@ -522,12 +535,84 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 	return pg_ls_dir(fcinfo);
 }
 
-/* Generic function to return a directory listing of files */
+/*
+ * Update fctx->path and stat with next filename.
+ * Directories are descended into.
+ * The current dir to read from is at fctx[fctx->depth].
+ */
+static int
+populate_paths(directory_fctx *fctx, FuncCallContext *funcctx)
+{
+	struct dirent *de;
+
+	for (;;)
+	{
+		char		path[MAXPGPATH];
+		DIR			*dirdesc = llast(fctx->dirdesc);
+		char		*location = llast(fctx->location);
+
+		Assert(list_length(fctx->dirdesc) == list_length(fctx->location));
+
+		if ((de = ReadDir(dirdesc, location)) == NULL)
+		{
+			/*
+			 * Read to the end of the dir on the top of the stack, now move to
+			 * the next dir.
+			 */
+			if (list_length(fctx->dirdesc) == 1)
+				return 0;
+			FreeDir(llast(fctx->dirdesc));
+			fctx->dirdesc = list_delete_last(fctx->dirdesc);
+			pfree(location);
+			fctx->location = list_delete_last(fctx->location);
+			continue;
+		}
+
+		/* Skip hidden files */
+		if (de->d_name[0] == '.')
+			continue;
+
+		/* Get the file info */
+		snprintf(path, sizeof(path), "%s/%s", location, de->d_name);
+		if (stat(path, &fctx->stat) < 0)
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not stat file \"%s\": %m", path)));
+
+		/* Ignore anything but regular files, or dirs, if requested */
+		if (S_ISDIR(fctx->stat.st_mode))
+		{
+			if (fctx->dir_action == DIR_HIDE)
+				continue;
+			else if (fctx->dir_action == DIR_DESCEND)
+			{
+				/* Reallocate location and dirdesc whenever descending */
+				MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+				fctx->location = lappend(fctx->location, pstrdup(path));
+				fctx->dirdesc = lappend(fctx->dirdesc, AllocateDir(path));
+				MemoryContextSwitchTo(oldcontext);
+			}
+		} else if (!S_ISREG(fctx->stat.st_mode))
+			continue;
+
+		/* Do not show the initial dir or slash */
+		fctx->path = pstrdup(path + 1 + strlen(linitial(fctx->location)));
+		return 1;
+	}
+}
+
+/*
+ * Generic function to return a directory listing of files
+ *
+ * if missing_ok, then fail silently if the dir doesn't exist, else error.
+ *
+ * if dir_action!=DIR_HIDE, then include a 4th column indicating whether path
+ * is a directory, and recurse if DIR_DESCEND.
+ */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, enum dir_action dir_action)
 {
 	FuncCallContext *funcctx;
-	struct dirent *de;
 	directory_fctx *fctx;
 
 	if (SRF_IS_FIRSTCALL())
@@ -540,19 +625,24 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		fctx = palloc(sizeof(directory_fctx));
 
-		tupdesc = CreateTemplateTupleDesc(3);
+		tupdesc = CreateTemplateTupleDesc(dir_action == DIR_HIDE ? 3 : 4);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
 						   TEXTOID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
 						   INT8OID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
 						   TIMESTAMPTZOID, -1, 0);
+		if (dir_action != DIR_HIDE)
+			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+						   BOOLOID, -1, 0);
+
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
-		fctx->location = pstrdup(dir);
-		fctx->dirdesc = AllocateDir(fctx->location);
+		fctx->location = lappend(NIL, pstrdup(dir));
+		fctx->dirdesc = lappend(NIL, AllocateDir(dir));
+		fctx->dir_action = dir_action;
 
-		if (!fctx->dirdesc)
+		if (!linitial(fctx->dirdesc))
 		{
 			if (missing_ok && errno == ENOENT)
 			{
@@ -563,7 +653,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
+								dir)));
 		}
 
 		funcctx->user_fctx = fctx;
@@ -573,39 +663,26 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	funcctx = SRF_PERCALL_SETUP();
 	fctx = (directory_fctx *) funcctx->user_fctx;
 
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+	if (populate_paths(fctx, funcctx))
 	{
-		Datum		values[3];
-		bool		nulls[3];
-		char		path[MAXPGPATH * 2];
-		struct stat attrib;
+		Datum		values[4];
+		bool		nulls[4] = {0};
 		HeapTuple	tuple;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
-			continue;
-
-		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", fctx->location, de->d_name);
-		if (stat(path, &attrib) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", path)));
-
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-		memset(nulls, 0, sizeof(nulls));
+		values[0] = CStringGetTextDatum(fctx->path);
+		values[1] = Int64GetDatum((int64) fctx->stat.st_size);
+		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fctx->stat.st_mtime));
+		if (dir_action != DIR_HIDE)
+			values[3] = BoolGetDatum(S_ISDIR(fctx->stat.st_mode));
 
 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+		pfree(fctx->path);
 		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
 	}
 
-	FreeDir(fctx->dirdesc);
+	FreeDir(linitial(fctx->dirdesc));
+	list_free(fctx->dirdesc);
+	list_free_deep(fctx->location);
 	SRF_RETURN_DONE(funcctx);
 }
 
@@ -613,14 +690,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, false, false);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, false, DIR_HIDE);
 }
 
 /*
@@ -638,7 +715,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path, true, DIR_DESCEND);
 }
 
 /*
@@ -667,5 +744,5 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, false);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7b7b..0dbce4ba09 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10739,13 +10739,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 
 # hash partitioning constraint function
-- 
2.17.0

  [text/x-diff] v7-0005-Change-logdir-and-archive_statusdir-to-include-di.patch (4.1K, ../../20200306233507.GN684@telsasoft.com/6-v7-0005-Change-logdir-and-archive_statusdir-to-include-di.patch)
  download | inline diff:
From 30a6473462e84ae700fb63269bb5669c3d1925be Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Thu, 5 Mar 2020 15:17:28 -0600
Subject: [PATCH v7 5/6] Change logdir and archive_statusdir to include dirs..

*and* add a column indicating *whether* a path is a dir..

The previous commit changes to always *show* directories (and optionally
recurse).

XXX: also need to update docs for new column
---
 doc/src/sgml/func.sgml          | 4 ++--
 src/backend/utils/adt/genfile.c | 4 ++--
 src/include/catalog/pg_proc.dat | 8 ++++----
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 15f4908924..2b920ebd12 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21451,7 +21451,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     (mtime) of each file in the log directory. By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
@@ -21476,7 +21476,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0207ac6be8..9d99528aa5 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -690,7 +690,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, enum
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, false, DIR_ONLY);
 }
 
 /* Function to return the list of files in the WAL directory */
@@ -744,5 +744,5 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, DIR_ONLY);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0dbce4ba09..fdf86e1eaf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10723,8 +10723,8 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
@@ -10733,8 +10733,8 @@
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
-- 
2.17.0

  [text/x-diff] v7-0006-Change-pg_ls_logdir-to-ignore-error-if-initial-to.patch (880B, ../../20200306233507.GN684@telsasoft.com/7-v7-0006-Change-pg_ls_logdir-to-ignore-error-if-initial-to.patch)
  download | inline diff:
From 458da7d43d5dcd91a1345a3a25ecc07f3274dade Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v7 6/6] Change pg_ls_logdir to ignore error if initial/top dir
 is missing

---
 src/backend/utils/adt/genfile.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9d99528aa5..e15ad1827c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -690,7 +690,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, enum
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false, DIR_ONLY);
+	return pg_ls_dir_files(fcinfo, Log_directory, true, DIR_ONLY);
 }
 
 /* Function to return the list of files in the WAL directory */
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-07 14:14   ` Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 17:10     ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 2 replies; 70+ messages in thread

From: Fabien COELHO @ 2020-03-07 14:14 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>


Hello Justin,

Some feedback about the v7 patch set.

About v7.1, seems ok.

About v7.2 & v7.3 seems ok, altought the two could be merged.

About v7.4:

The documentation sentences could probably be improved "for for", "used 
... used". Maybe:

   For the temporary directory for <parameter>tablespace</parameter>, ...
->
   For <parameter>tablespace</parameter> temporary directory, ...

   Directories are used for temporary files used by parallel
   processes, and are shown recursively.
->
   Directories holding temporary files used by parallel
   processes are shown recursively.

It seems that lists are used as FIFO structures by appending, fetching & 
deleting last, all of which are O(n). ISTM it would be better to use the 
head of the list by inserting, getting and deleting first, which are O(1).

ISTM that several instances of: "pg_ls_dir_files(..., true, false);" 
should be "pg_ls_dir_files(..., true, DIR_HIDE);".

About v7.5 looks like a doc update which should be merged with v7.4.

Alas, ISTM that there are no tests on any of these functions:-(

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-03-07 17:10     ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 17:40       ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-07 17:10 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Sat, Mar 07, 2020 at 03:14:37PM +0100, Fabien COELHO wrote:
> Some feedback about the v7 patch set.

Thanks for looking again

> About v7.1, seems ok.
> 
> About v7.2 & v7.3 seems ok, altought the two could be merged.

These are separate since I proprose that one should be backpatched to v12 and
the other to v10.

> About v7.4:
...
> It seems that lists are used as FIFO structures by appending, fetching &
> deleting last, all of which are O(n). ISTM it would be better to use the
> head of the list by inserting, getting and deleting first, which are O(1).

I think you're referring to linked lists, but pglists are now arrays, for which
that's backwards.  See 1cff1b95a and d97b714a2.  For example, list_delete_last
says:
 * This is the opposite of list_delete_first(), but is noticeably cheaper
 * with a long list, since no data need be moved.

> ISTM that several instances of: "pg_ls_dir_files(..., true, false);" should
> be "pg_ls_dir_files(..., true, DIR_HIDE);".

Oops, that affects an intermediate commit and maybe due to merge conflict.
Thanks.

> About v7.5 looks like a doc update which should be merged with v7.4.

No, v7.5 updates pg_proc.dat and changes the return type of two functions.
It's a short commit since all the infrastructure is implemented to make the
functions do whatever we want.  But it's deliberately separate since I'm
proposing a breaking change, and one that hasn't been discussed until now.

> Alas, ISTM that there are no tests on any of these functions:-(

Yeah.  Everything that includes any output is going to include timestamps;
those could be filtered out.  waldir is going to have random filenames, and a
differing number of rows.  But we should exercize pg_ls_dir_files at least
once..

My previous version had a bug with ignore_missing with pg_ls_tmpdir, which
would've been caught by a test like:
SELECT FROM pg_ls_tmpdir() WHERE name='Does not exist'; -- Never true, so the function runs to completion but returns zero rows.

The 0006 commit changes that for logdir, too.  Without 0006, that will ERROR if
the dir doesn't exist (which I think would be the default during regression
tests).

It'd be nice to run pg_ls_tmpdir before the tmpdir exists, and again
afterwards.  But I'm having trouble finding a single place to put it.  The
closest I can find is dbsize.sql.  Any ideas ?

-- 
Justin





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 17:10     ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-07 17:40       ` Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 70+ messages in thread

From: Fabien COELHO @ 2020-03-07 17:40 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>


>> It seems that lists are used as FIFO structures by appending, fetching &
>> deleting last, all of which are O(n). ISTM it would be better to use the
>> head of the list by inserting, getting and deleting first, which are O(1).
>
> I think you're referring to linked lists, but pglists are now arrays,

Ok… I forgot about this change, so my point is void, you took the right 
one.

-- 
Fabien.

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-03-07 21:40     ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-08 08:02       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-03-07 21:40 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Sat, Mar 07, 2020 at 03:14:37PM +0100, Fabien COELHO wrote:
> The documentation sentences could probably be improved "for for", "used ...
> used". Maybe:

> ISTM that several instances of: "pg_ls_dir_files(..., true, false);" should
> be "pg_ls_dir_files(..., true, DIR_HIDE);".

> Alas, ISTM that there are no tests on any of these functions:-(

Addressed these.

And reordered the last two commits to demonstrate and exercize the behavior
change in regress test.

-- 
Justin

Attachments:

  [text/x-diff] v8-0001-BUG-in-errmsg.patch (948B, ../../20200307214010.GB1357@telsasoft.com/2-v8-0001-BUG-in-errmsg.patch)
  download | inline diff:
From a5b9a03445d1c768662cafebd8ab3bd7a62890aa Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 27 Dec 2019 23:34:14 -0600
Subject: [PATCH v8 1/6] BUG: in errmsg

Note there's two changes here.
Should backpatch to v12, where pg_ls_tmpdir was added.
---
 src/backend/utils/adt/genfile.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3741b87486..897b11a77d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -590,7 +590,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 		if (stat(path, &attrib) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
-					 errmsg("could not stat directory \"%s\": %m", dir)));
+					 errmsg("could not stat file \"%s\": %m", path)));
 
 		/* Ignore anything but regular files */
 		if (!S_ISREG(attrib.st_mode))
-- 
2.17.0

  [text/x-diff] v8-0002-Document-historic-behavior-about-hiding-directori.patch (1.7K, ../../20200307214010.GB1357@telsasoft.com/3-v8-0002-Document-historic-behavior-about-hiding-directori.patch)
  download | inline diff:
From 6ea85ec0a267930320b8454a33bca368a8544a2d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 16:50:07 -0600
Subject: [PATCH v8 2/6] Document historic behavior about hiding directories
 and special files

Should backpatch to v10: tmpdir, waldir and archive_statusdir
---
 doc/src/sgml/func.sgml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..4c0ea5ab3f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21450,6 +21450,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     (mtime) of each file in the log directory. By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21461,6 +21462,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21473,6 +21475,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v8-0003-Document-historic-behavior-about-hiding-directori.patch (903B, ../../20200307214010.GB1357@telsasoft.com/4-v8-0003-Document-historic-behavior-about-hiding-directori.patch)
  download | inline diff:
From 5250d637493627f1ff3587bc73dd598bc1ca3ffc Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:12:04 -0600
Subject: [PATCH v8 3/6] Document historic behavior about hiding directories
 and special files

Should backpatch to v12: tmpdir
---
 doc/src/sgml/func.sgml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c0ea5ab3f..fc4d7f0f78 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21489,6 +21489,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v8-0004-pg_ls_tmpdir-to-show-directories-recursively.patch (14.9K, ../../20200307214010.GB1357@telsasoft.com/5-v8-0004-pg_ls_tmpdir-to-show-directories-recursively.patch)
  download | inline diff:
From 70183f1ba1eb33e5c279a6d22a56fcaebdbfbb97 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 14 Dec 2019 16:22:15 -0600
Subject: [PATCH v8 4/6] pg_ls_tmpdir to show directories recursively

See also 9cd92d1a33699f86aa53d44ab04cc3eb50c18d11

Discussion
https://www.postgresql.org/message-id/flat/20191213053931.GV2082@telsasoft.com
https://www.postgresql.org/message-id/flat/20191227170220.GE12890@telsasoft.com

Need catversion bump
---
 doc/src/sgml/func.sgml                    |  14 +-
 src/backend/utils/adt/genfile.c           | 167 ++++++++++++++++------
 src/include/catalog/pg_proc.dat           |   8 +-
 src/test/regress/input/tablespace.source  |   5 +
 src/test/regress/output/tablespace.source |   8 ++
 5 files changed, 147 insertions(+), 55 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..234c9c1699 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21382,8 +21382,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and boolean
+        indicating if it is a directory.  Directories are shown recursively.  If
         <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is used.  Access is granted
         to members of the <literal>pg_monitor</literal> role and may be
@@ -21482,14 +21483,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes, and are shown recursively.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 897b11a77d..a534d75dd8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -35,11 +35,23 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+enum dir_action {
+	DIR_HIDE,
+	DIR_ONLY,
+	DIR_DESCEND,
+};
+
 typedef struct
 {
-	char	   *location;
-	DIR		   *dirdesc;
+	/* Stack of opened dirs */
+	List		*location;
+	List		*dirdesc;
 	bool		include_dot_dirs;
+
+	/* Used in ls_dir_files: */
+	enum dir_action dir_action;
+	char		*path;
+	struct stat	stat;
 } directory_fctx;
 
 
@@ -469,10 +481,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
 
 		fctx = palloc(sizeof(directory_fctx));
-		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
+		fctx->location = lappend(NIL, convert_and_check_filename(PG_GETARG_TEXT_PP(0)));
+		fctx->dirdesc = lappend(NIL, AllocateDir(linitial(fctx->location)));
 		fctx->include_dot_dirs = include_dot_dirs;
-		fctx->dirdesc = AllocateDir(fctx->location);
 
 		if (!fctx->dirdesc)
 		{
@@ -485,7 +496,7 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
+								(char*)linitial(fctx->location))));
 		}
 		funcctx->user_fctx = fctx;
 		MemoryContextSwitchTo(oldcontext);
@@ -494,7 +505,7 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 	funcctx = SRF_PERCALL_SETUP();
 	fctx = (directory_fctx *) funcctx->user_fctx;
 
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+	while ((de = ReadDir(linitial(fctx->dirdesc), linitial(fctx->location))) != NULL)
 	{
 		if (!fctx->include_dot_dirs &&
 			(strcmp(de->d_name, ".") == 0 ||
@@ -504,7 +515,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
 	}
 
-	FreeDir(fctx->dirdesc);
+	FreeDir(linitial(fctx->dirdesc));
+	list_free(fctx->dirdesc);
+	list_free_deep(fctx->location);
 
 	SRF_RETURN_DONE(funcctx);
 }
@@ -522,12 +535,84 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 	return pg_ls_dir(fcinfo);
 }
 
-/* Generic function to return a directory listing of files */
+/*
+ * Update fctx->path and stat with next filename.
+ * Directories are descended into.
+ * The current dir to read from is at fctx[fctx->depth].
+ */
+static int
+populate_paths(directory_fctx *fctx, FuncCallContext *funcctx)
+{
+	struct dirent *de;
+
+	for (;;)
+	{
+		char		path[MAXPGPATH];
+		DIR			*dirdesc = llast(fctx->dirdesc);
+		char		*location = llast(fctx->location);
+
+		Assert(list_length(fctx->dirdesc) == list_length(fctx->location));
+
+		if ((de = ReadDir(dirdesc, location)) == NULL)
+		{
+			/*
+			 * Read to the end of the dir on the top of the stack, now move to
+			 * the next dir.
+			 */
+			if (list_length(fctx->dirdesc) == 1)
+				return 0;
+			FreeDir(llast(fctx->dirdesc));
+			fctx->dirdesc = list_delete_last(fctx->dirdesc);
+			pfree(location);
+			fctx->location = list_delete_last(fctx->location);
+			continue;
+		}
+
+		/* Skip hidden files */
+		if (de->d_name[0] == '.')
+			continue;
+
+		/* Get the file info */
+		snprintf(path, sizeof(path), "%s/%s", location, de->d_name);
+		if (stat(path, &fctx->stat) < 0)
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not stat file \"%s\": %m", path)));
+
+		/* Ignore anything but regular files, or dirs, if requested */
+		if (S_ISDIR(fctx->stat.st_mode))
+		{
+			if (fctx->dir_action == DIR_HIDE)
+				continue;
+			else if (fctx->dir_action == DIR_DESCEND)
+			{
+				MemoryContext oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+				fctx->location = lappend(fctx->location, pstrdup(path));
+				fctx->dirdesc = lappend(fctx->dirdesc, AllocateDir(path));
+				MemoryContextSwitchTo(oldcontext);
+			}
+		} else if (!S_ISREG(fctx->stat.st_mode))
+			continue;
+
+		/* Do not show the initial dir or slash */
+		fctx->path = pstrdup(path + 1 + strlen(linitial(fctx->location)));
+		return 1;
+	}
+}
+
+/*
+ * Generic function to return a directory listing of files
+ *
+ * if missing_ok, then fail silently if the dir doesn't exist, else error.
+ *
+ * if dir_action!=DIR_HIDE, then include a 4th column indicating whether path
+ * is a directory, and recurse if DIR_DESCEND.
+ */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok,
+		enum dir_action dir_action)
 {
 	FuncCallContext *funcctx;
-	struct dirent *de;
 	directory_fctx *fctx;
 
 	if (SRF_IS_FIRSTCALL())
@@ -540,19 +625,24 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		fctx = palloc(sizeof(directory_fctx));
 
-		tupdesc = CreateTemplateTupleDesc(3);
+		tupdesc = CreateTemplateTupleDesc(dir_action == DIR_HIDE ? 3 : 4);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
 						   TEXTOID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
 						   INT8OID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
 						   TIMESTAMPTZOID, -1, 0);
+		if (dir_action != DIR_HIDE)
+			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+						   BOOLOID, -1, 0);
+
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
-		fctx->location = pstrdup(dir);
-		fctx->dirdesc = AllocateDir(fctx->location);
+		fctx->location = lappend(NIL, pstrdup(dir));
+		fctx->dirdesc = lappend(NIL, AllocateDir(dir));
+		fctx->dir_action = dir_action;
 
-		if (!fctx->dirdesc)
+		if (!linitial(fctx->dirdesc))
 		{
 			if (missing_ok && errno == ENOENT)
 			{
@@ -563,7 +653,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
+								dir)));
 		}
 
 		funcctx->user_fctx = fctx;
@@ -573,39 +663,26 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	funcctx = SRF_PERCALL_SETUP();
 	fctx = (directory_fctx *) funcctx->user_fctx;
 
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
+	if (populate_paths(fctx, funcctx))
 	{
-		Datum		values[3];
-		bool		nulls[3];
-		char		path[MAXPGPATH * 2];
-		struct stat attrib;
+		Datum		values[4];
+		bool		nulls[4] = {0};
 		HeapTuple	tuple;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
-			continue;
-
-		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", fctx->location, de->d_name);
-		if (stat(path, &attrib) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", path)));
-
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-		memset(nulls, 0, sizeof(nulls));
+		values[0] = CStringGetTextDatum(fctx->path);
+		values[1] = Int64GetDatum((int64) fctx->stat.st_size);
+		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fctx->stat.st_mtime));
+		if (dir_action != DIR_HIDE)
+			values[3] = BoolGetDatum(S_ISDIR(fctx->stat.st_mode));
 
 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+		pfree(fctx->path);
 		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
 	}
 
-	FreeDir(fctx->dirdesc);
+	FreeDir(linitial(fctx->dirdesc));
+	list_free(fctx->dirdesc);
+	list_free_deep(fctx->location);
 	SRF_RETURN_DONE(funcctx);
 }
 
@@ -613,14 +690,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, false, DIR_HIDE);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, false, DIR_HIDE);
 }
 
 /*
@@ -638,7 +715,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path, true, DIR_DESCEND);
 }
 
 /*
@@ -667,5 +744,5 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, DIR_HIDE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07a86c7b7b..0dbce4ba09 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10739,13 +10739,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..a7f34c5dd5 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1)b , pg_ls_tmpdir(oid)c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..edcdb4f32a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1)b , pg_ls_tmpdir(oid)c WHERE c.name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v8-0005-Change-pg_ls_logdir-to-ignore-error-if-initial-to.patch (3.0K, ../../20200307214010.GB1357@telsasoft.com/6-v8-0005-Change-pg_ls_logdir-to-ignore-error-if-initial-to.patch)
  download | inline diff:
From 3ae462152696fe4ac19c0faad9807a2d06849a75 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v8 5/6] Change pg_ls_logdir to ignore error if initial/top dir
 is missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a534d75dd8..03656a28f9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -690,7 +690,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok,
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false, DIR_HIDE);
+	return pg_ls_dir_files(fcinfo, Log_directory, true, DIR_HIDE);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a7f34c5dd5..88121f88db 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1)b , pg_ls_tmpdir(oid)c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index edcdb4f32a..25a0081927 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v8-0006-Change-logdir-and-archive_statusdir-to-include-di.patch (7.2K, ../../20200307214010.GB1357@telsasoft.com/7-v8-0006-Change-logdir-and-archive_statusdir-to-include-di.patch)
  download | inline diff:
From 98d4a2c9768770f1325e37fab553198c1fa19da7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Thu, 5 Mar 2020 15:17:28 -0600
Subject: [PATCH v8 6/6] Change logdir and archive_statusdir to include dirs..

and add a column indicating *whether* a path is a dir.

Need catversion bump
---
 doc/src/sgml/func.sgml                    | 31 +++++++++++++----------
 src/backend/utils/adt/genfile.c           |  4 +--
 src/include/catalog/pg_proc.dat           |  8 +++---
 src/test/regress/output/tablespace.source |  4 +--
 4 files changed, 26 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 234c9c1699..dd3faa4e7c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21348,8 +21348,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21370,10 +21371,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
-        <literal>pg_monitor</literal> role and may be granted to other
-        non-superuser roles.
+        For each file in the WAL archive status directory, list the file's name,
+        size, last modification time, and boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21447,11 +21448,13 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with the file's name, size, last modified time (mtime) and boolean
+    indicating if the file is a directory.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
@@ -21470,13 +21473,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory <filename>pg_wal/archive_status</filename> along with
+    the file's name, size, last modified time (mtime) and boolean indicating if
+    the file is a directory.
+    By default only
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 03656a28f9..3da901f8d6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -690,7 +690,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok,
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, true, DIR_HIDE);
+	return pg_ls_dir_files(fcinfo, Log_directory, true, DIR_ONLY);
 }
 
 /* Function to return the list of files in the WAL directory */
@@ -744,5 +744,5 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, DIR_HIDE);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, DIR_ONLY);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0dbce4ba09..fdf86e1eaf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10723,8 +10723,8 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
@@ -10733,8 +10733,8 @@
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 25a0081927..a5017e5f9d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -24,8 +24,8 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-08 08:02       ` Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 0 replies; 70+ messages in thread

From: Fabien COELHO @ 2020-03-08 08:02 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>


Hello Justin,

Patch series applies cleanly. The last status compiles and passes "make 
check". A few more comments:

* v8.[123] ok.

* v8.4

Avoid using the type name as a field name? "enum dir_action dir_action;" 
-> "enum dir_action action", or maybe rename "dir_action" enum 
"dir_action_t".

About pg_ls_dir:

"if (!fctx->dirdesc)" I do not think that is true even if AllocateDir 
failed, the list exists anyway. ISTM it should be linitial which is NULL 
in that case.

Given the overlap between pg_ls_dir and pg_ls_dir_files, ISTM that the 
former should call the slightly extended later with appropriate flags.

About populate_paths:

function is a little bit strange to me, ISTM it would deserve more 
comments.

I'm not sure the name reflect what it does. For instance, ISTM that it 
does one thing, but the name is plural. Maybe "move_to_next_path" or 
"update_current_path" or something?

It returns an int which can only be 0 or 1, which smells like an bool. 
What is this int/bool is not told in the function head comment. I guess it 
is whether the path was updated. When it returns false, the list length is 
down to one.

Shouldn't AllocateDir be tested for bad result? Maybe it is a dir but you 
do not have perms to open it? Or give a comment about why it cannot 
happen?

later, good, at least the function is called, even if it is only for an 
error case. Maybe some non empty coverage tests could be added with a 
"count(*) > 0" on not is_dir or maybe "count(*) = 0" on is_dir, for 
instance?

   (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace'
   UNION SELECT 0 ORDER BY 1 DESC LIMIT 1)b

The 'b' glued to the ')' looks pretty strange. I'd suggest ") AS b". 
Reusing the same alias twice could be avoided for clarity, maybe.

* v8.[56]

I'd say that a behavior change which adds a column and a possibly a few 
rows is ok, especially as the tmpdir contains subdirs now.

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-10 18:30       ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-10 18:30 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

I took a step back, and I wondered whether we should add a generic function for
listing a dir with metadata, possibly instead of changing the existing
functions.  Then one could do pg_ls_dir_metadata('pg_wal',false,false);

Since pg8.1, we have pg_ls_dir() to show a list of files.  Since pg10, we've
had pg_ls_logdir and pg_ls_waldir, which show not only file names but also
(some) metadata (size, mtime).  And since pg12, we've had pg_ls_tmpfile and
pg_ls_archive_statusdir, which also show metadata.

...but there's no a function which lists the metadata of an directory other
than tmp, wal, log.

One can do this:
|SELECT b.*, c.* FROM (SELECT 'base' a)a, LATERAL (SELECT a||'/'||pg_ls_dir(a.a)b)b, pg_stat_file(b)c;
..but that's not as helpful as allowing:
|SELECT * FROM pg_ls_dir_metadata('.',true,true);

There's also no function which recurses into an arbitrary directory, so it
seems shortsighted to provide a function to recursively list a tmpdir.

Also, since pg_ls_dir_metadata indicates whether the path is a dir, one can
write a SQL function to show the dir recursively.  It'd be trivial to plug in
wal/log/tmp (it seems like tmpdirs of other tablespace's are not entirely
trivial).
|SELECT * FROM pg_ls_dir_recurse('base/pgsql_tmp');

Also, on a neighboring thread[1], Tom indicated that the pg_ls_* functions
should enumerate all files during the initial call, which sounds like a bad
idea when recursively showing directories.  If we add a function recursing into
a directory, we'd need to discuss all the flags to expose to it, like recurse,
ignore_errors, one_filesystem?, show_dotfiles (and eventually bikeshed all the
rest of the flags in find(1)).

My initial patch [2] changed ls_tmpdir to show metadata columns including
is_dir, but not decend.  It's pretty unfortunate if a function called
pg_ls_tmpdir hides shared filesets, so maybe it really is best to change that
(it's new in v12).

I'm interested to in feedback on the alternative approach, as attached.  The
final patch to include all the rest of columns shown by pg_stat_file() is more
of an idea/proposal and not sure if it'll be desirable.  But pg_ls_tmpdir() is
essentially the same as my v1 patch.

This is intended to be mostly independent of any fix to the WARNING I reported
[1].  Since my patch collapses pg_ls_dir into pg_ls_dir_files, we'd only need
to fix one place.  I'm planning to eventually look into Tom's suggestion of
returning tuplestore to fix that, and maybe rebase this patchset on top of
that.

-- 
Justin

[1] https://www.postgresql.org/message-id/flat/20200308173103.GC1357%40telsasoft.com
[2] https://www.postgresql.org/message-id/20191214224735.GA28433%40telsasoft.com

Attachments:

  [text/x-diff] v9-0001-BUG-in-errmsg.patch (950B, ../../20200310183037.GA29065@telsasoft.com/2-v9-0001-BUG-in-errmsg.patch)
  download | inline diff:
From 2c4b2c408490ecde3cfb4e336a78942f7a6f8197 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 27 Dec 2019 23:34:14 -0600
Subject: [PATCH v9 01/11] BUG: in errmsg

Note there's two changes here.
Should backpatch to v12, where pg_ls_tmpdir was added.
---
 src/backend/utils/adt/genfile.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3741b87486..897b11a77d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -590,7 +590,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 		if (stat(path, &attrib) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
-					 errmsg("could not stat directory \"%s\": %m", dir)));
+					 errmsg("could not stat file \"%s\": %m", path)));
 
 		/* Ignore anything but regular files */
 		if (!S_ISREG(attrib.st_mode))
-- 
2.17.0

  [text/x-diff] v9-0002-Document-historic-behavior-about-hiding-directori.patch (1.7K, ../../20200310183037.GA29065@telsasoft.com/3-v9-0002-Document-historic-behavior-about-hiding-directori.patch)
  download | inline diff:
From f3ef0c6ff664f2f26e95ce97e8b50a813bd1aab8 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 16:50:07 -0600
Subject: [PATCH v9 02/11] Document historic behavior about hiding directories
 and special files

Should backpatch to v10: tmpdir, waldir and archive_statusdir
---
 doc/src/sgml/func.sgml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..4c0ea5ab3f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21450,6 +21450,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     (mtime) of each file in the log directory. By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21461,6 +21462,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21473,6 +21475,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v9-0003-Document-historic-behavior-about-hiding-directori.patch (905B, ../../20200310183037.GA29065@telsasoft.com/4-v9-0003-Document-historic-behavior-about-hiding-directori.patch)
  download | inline diff:
From b1fb8ef86178fff474f0205143b6f37e156d0d9f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:12:04 -0600
Subject: [PATCH v9 03/11] Document historic behavior about hiding directories
 and special files

Should backpatch to v12: tmpdir
---
 doc/src/sgml/func.sgml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c0ea5ab3f..fc4d7f0f78 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21489,6 +21489,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v9-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-me.patch (5.1K, ../../20200310183037.GA29065@telsasoft.com/5-v9-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-me.patch)
  download | inline diff:
From f1350c7880c8ca94358c114808f90d9fc94aa08f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 17:15:02 -0500
Subject: [PATCH v9 04/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Need catversion bumped?
---
 src/backend/utils/adt/genfile.c | 51 ++++++++++++++++++++++++++-------
 src/include/catalog/pg_proc.dat |  6 ++++
 2 files changed, 47 insertions(+), 10 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 897b11a77d..4699aea568 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -524,7 +524,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 
 /* Generic function to return a directory listing of files */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool dir_ok)
 {
 	FuncCallContext *funcctx;
 	struct dirent *de;
@@ -540,13 +540,17 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		fctx = palloc(sizeof(directory_fctx));
 
-		tupdesc = CreateTemplateTupleDesc(3);
+		tupdesc = CreateTemplateTupleDesc(dir_ok ? 4:3);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
 						   TEXTOID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
 						   INT8OID, -1, 0);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
 						   TIMESTAMPTZOID, -1, 0);
+		if (dir_ok)
+			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+					BOOLOID, -1, 0);
+
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
 		fctx->location = pstrdup(dir);
@@ -575,8 +579,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 		HeapTuple	tuple;
@@ -592,13 +596,21 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (!dir_ok)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+			/* Ignore anything but regular files */
 			continue;
 
 		values[0] = CStringGetTextDatum(de->d_name);
 		values[1] = Int64GetDatum((int64) attrib.st_size);
 		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (dir_ok)
+			values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
@@ -613,14 +625,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, false, false);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, false, false);
 }
 
 /*
@@ -638,7 +650,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path, true, false);
 }
 
 /*
@@ -667,5 +679,24 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, false);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+	bool	missing_ok = false;
+	bool	include_dot_dirs = false;
+
+	if (!PG_ARGISNULL(1))
+		missing_ok = PG_GETARG_BOOL(1);
+	if (!PG_ARGISNULL(2))
+		/* XXX: Not implemented */
+		include_dot_dirs = PG_GETARG_BOOL(2);
+
+	return pg_ls_dir_files(fcinfo, dirname, missing_ok, true);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
-- 
2.17.0

  [text/x-diff] v9-0005-Add-tests-exercizing-pg_ls_tmpdir.patch (2.8K, ../../20200310183037.GA29065@telsasoft.com/6-v9-0005-Add-tests-exercizing-pg_ls_tmpdir.patch)
  download | inline diff:
From a90befd3d648bb591af4aba2f903e90b02640bb0 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v9 05/11] Add tests exercizing pg_ls_tmpdir..

..and its backing function pg_ls_dir_files
---
 src/test/regress/input/tablespace.source  | 5 +++++
 src/test/regress/output/tablespace.source | 8 ++++++++
 2 files changed, 13 insertions(+)

diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v9-0006-pg_ls_tmpdir-to-show-isdir-argument.patch (6.5K, ../../20200310183037.GA29065@telsasoft.com/7-v9-0006-pg_ls_tmpdir-to-show-isdir-argument.patch)
  download | inline diff:
From 018b74872089021ba1319aef01ebfb8154ee4b58 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v9 06/11] pg_ls_tmpdir to show "isdir" argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

Note that "dir_ok" means both: do not skip dirs, *and* show "isdir" column.
The other functions both skip dirs and only show 3 columns, and ls_tmpdir is
changing to both show dirs and show 4 columns.  It wouldn't make sense to skip
dirs and then show a 4th column always false.

Need catversion bump
---
 doc/src/sgml/func.sgml                    | 22 ++++++++++++----------
 src/backend/utils/adt/genfile.c           |  4 ++--
 src/include/catalog/pg_proc.dat           |  8 ++++----
 src/test/regress/output/tablespace.source |  4 ++--
 4 files changed, 20 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..7c73d3b82a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21382,12 +21382,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21482,14 +21482,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4699aea568..7168e249a4 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -522,7 +522,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 	return pg_ls_dir(fcinfo);
 }
 
-/* Generic function to return a directory listing of files */
+/* Generic function to return a directory listing of files (and optionally dirs) */
 static Datum
 pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool dir_ok)
 {
@@ -650,7 +650,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true, false);
+	return pg_ls_dir_files(fcinfo, path, true, true);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0a1859f709..7789d029ea 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10733,13 +10733,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v9-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (2.8K, ../../20200310183037.GA29065@telsasoft.com/8-v9-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 907f88c171bd22f382b707da1cdbbdfd63fb177a Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v9 07/11] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

TODO:
src/backend/catalog/system_views.sql:REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;

Need catversion bumped ?
---
 doc/src/sgml/func.sgml          | 13 +++++++++++++
 src/include/catalog/pg_proc.dat |  8 ++++++++
 2 files changed, 21 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c73d3b82a..672cbab7b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21442,6 +21442,19 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    <parameter>missing_ok</parameter> ???
+    To recusively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp')dir  FROM pg_tablespace b, pg_control_system() pcs, format('/PG_%s_%s', left(current_setting('server_version_num'), 2), catalog_version_no) suffix)dir, pg_ls_dir_recurse(dir)a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7789d029ea..cc2c6f6571 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6143,6 +6143,14 @@
   proname => 'pg_ls_dir', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'text', proargtypes => 'text bool bool',
   prosrc => 'pg_ls_dir' },
+
+{ oid => '8511', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false, true) UNION ALL SELECT x.name||'/'||a.name, a.size, a.modification, a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false, true)a WHERE x.isdir) SELECT * FROM x" },
+
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
   proargtypes => 'float8', prosrc => 'pg_sleep' },
-- 
2.17.0

  [text/x-diff] v9-0008-generalize-pg_ls_dir_files-and-retire-pg_ls_dir.patch (8.4K, ../../20200310183037.GA29065@telsasoft.com/9-v9-0008-generalize-pg_ls_dir_files-and-retire-pg_ls_dir.patch)
  download | inline diff:
From 78e8824063de0170022595644e1be68e341dcb10 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 23:51:22 -0500
Subject: [PATCH v9 08/11] generalize pg_ls_dir_files and retire pg_ls_dir

---
 src/backend/utils/adt/genfile.c | 172 +++++++++++++++-----------------
 1 file changed, 79 insertions(+), 93 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 7168e249a4..888560f78e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -42,6 +42,14 @@ typedef struct
 	bool		include_dot_dirs;
 } directory_fctx;
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	FLAG_ISDIR				(1<<0) /* Show column: isdir */
+#define	FLAG_METADATA			(1<<1) /* Show columns: mtime, size */
+#define	FLAG_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	FLAG_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	FLAG_SKIP_HIDDEN		(1<<4) /* Do not show anything begining with . */
+#define	FLAG_SKIP_DIRS			(1<<5) /* Do not show directories */
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -446,67 +454,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	FuncCallContext *funcctx;
-	struct dirent *de;
-	directory_fctx *fctx;
-	MemoryContext oldcontext;
-
-	if (SRF_IS_FIRSTCALL())
-	{
-		bool		missing_ok = false;
-		bool		include_dot_dirs = false;
-
-		/* check the optional arguments */
-		if (PG_NARGS() == 3)
-		{
-			if (!PG_ARGISNULL(1))
-				missing_ok = PG_GETARG_BOOL(1);
-			if (!PG_ARGISNULL(2))
-				include_dot_dirs = PG_GETARG_BOOL(2);
-		}
-
-		funcctx = SRF_FIRSTCALL_INIT();
-		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
-
-		fctx = palloc(sizeof(directory_fctx));
-		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-		fctx->include_dot_dirs = include_dot_dirs;
-		fctx->dirdesc = AllocateDir(fctx->location);
-
-		if (!fctx->dirdesc)
-		{
-			if (missing_ok && errno == ENOENT)
-			{
-				MemoryContextSwitchTo(oldcontext);
-				SRF_RETURN_DONE(funcctx);
-			}
-			else
-				ereport(ERROR,
-						(errcode_for_file_access(),
-						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
-		}
-		funcctx->user_fctx = fctx;
-		MemoryContextSwitchTo(oldcontext);
-	}
-
-	funcctx = SRF_PERCALL_SETUP();
-	fctx = (directory_fctx *) funcctx->user_fctx;
-
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
-	{
-		if (!fctx->include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
-	}
-
-	FreeDir(fctx->dirdesc);
-
-	SRF_RETURN_DONE(funcctx);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, FLAG_SKIP_DOT_DIRS);
 }
 
 /*
@@ -519,17 +469,44 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, FLAG_SKIP_DOT_DIRS);
 }
 
 /* Generic function to return a directory listing of files (and optionally dirs) */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool dir_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	FuncCallContext *funcctx;
 	struct dirent *de;
 	directory_fctx *fctx;
 
+	/* isdir depends on metadata */
+	Assert(!(flags&FLAG_ISDIR) || (flags&FLAG_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&FLAG_ISDIR) || !(flags&FLAG_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= FLAG_MISSING_OK;
+			else
+				flags &= ~FLAG_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~FLAG_SKIP_DOT_DIRS;
+			else
+				flags |= FLAG_SKIP_DOT_DIRS;
+		}
+	}
+
 	if (SRF_IS_FIRSTCALL())
 	{
 		MemoryContext oldcontext;
@@ -540,16 +517,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 
 		fctx = palloc(sizeof(directory_fctx));
 
-		tupdesc = CreateTemplateTupleDesc(dir_ok ? 4:3);
+		tupdesc = CreateTemplateTupleDesc((flags&FLAG_ISDIR) ? 4 :
+				(flags&FLAG_METADATA) ? 3 : 1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
 						   TEXTOID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
-						   INT8OID, -1, 0);
-		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
-						   TIMESTAMPTZOID, -1, 0);
-		if (dir_ok)
-			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
-					BOOLOID, -1, 0);
+		if (flags&FLAG_METADATA)
+		{
+			TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
+							   INT8OID, -1, 0);
+			TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
+							   TIMESTAMPTZOID, -1, 0);
+			if (flags&FLAG_ISDIR)
+				TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+						BOOLOID, -1, 0);
+		}
 
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
 
@@ -558,7 +539,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 
 		if (!fctx->dirdesc)
 		{
-			if (missing_ok && errno == ENOENT)
+			if (flags&FLAG_MISSING_OK && errno == ENOENT)
 			{
 				MemoryContextSwitchTo(oldcontext);
 				SRF_RETURN_DONE(funcctx);
@@ -585,8 +566,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 		struct stat attrib;
 		HeapTuple	tuple;
 
+		if (flags&FLAG_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
 		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		if (flags&FLAG_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -598,18 +585,21 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 
 		if (S_ISDIR(attrib.st_mode))
 		{
-			if (!dir_ok)
+			if (flags&FLAG_SKIP_DIRS)
 				continue;
 		}
 		else if (!S_ISREG(attrib.st_mode))
-			/* Ignore anything but regular files */
 			continue;
 
-		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-		if (dir_ok)
-			values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+		if (flags & FLAG_METADATA)
+		{
+			values[0] = CStringGetTextDatum(de->d_name);
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & FLAG_ISDIR)
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+		} else
+			SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
 
 		memset(nulls, 0, sizeof(nulls));
 
@@ -625,14 +615,16 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false, false);
+	return pg_ls_dir_files(fcinfo, Log_directory,
+			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR,
+			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /*
@@ -650,7 +642,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true, true);
+	return pg_ls_dir_files(fcinfo, path,
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_METADATA|FLAG_ISDIR);
 }
 
 /*
@@ -679,7 +672,8 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /*
@@ -689,14 +683,6 @@ Datum
 pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 {
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-	bool	missing_ok = false;
-	bool	include_dot_dirs = false;
-
-	if (!PG_ARGISNULL(1))
-		missing_ok = PG_GETARG_BOOL(1);
-	if (!PG_ARGISNULL(2))
-		/* XXX: Not implemented */
-		include_dot_dirs = PG_GETARG_BOOL(2);
 
-	return pg_ls_dir_files(fcinfo, dirname, missing_ok, true);
+	return pg_ls_dir_files(fcinfo, dirname, FLAG_METADATA|FLAG_ISDIR);
 }
-- 
2.17.0

  [text/x-diff] v9-0009-pg_ls_logdir-to-ignore-error-if-initial-top-dir-i.patch (3.0K, ../../20200310183037.GA29065@telsasoft.com/10-v9-0009-pg_ls_logdir-to-ignore-error-if-initial-top-dir-i.patch)
  download | inline diff:
From 43c8bc8611b53ff172e5c7caa052d80d9e4ac3e9 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v9 09/11] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 888560f78e..4ce39516d7 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -616,7 +616,7 @@ Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, Log_directory,
-			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..dacaafcae6 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v9-0010-pg_ls_-dir-to-show-directories-and-isdir-column.patch (13.3K, ../../20200310183037.GA29065@telsasoft.com/11-v9-0010-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From adf2977cf2553a10a9c2cb6cb3a52590365783bf Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v9 10/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

..FLAG_ISDIR is collapsed into FLAG_METADATA.

Need catversion bump
---
 doc/src/sgml/func.sgml          | 39 +++++++++++++++++--------------
 src/backend/utils/adt/genfile.c | 41 +++++++++++----------------------
 src/include/catalog/pg_proc.dat | 20 ++++++++--------
 3 files changed, 46 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 672cbab7b9..d0b782d803 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21348,8 +21348,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21359,8 +21360,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21370,8 +21372,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21459,36 +21462,38 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4ce39516d7..387114d4ee 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -44,12 +44,10 @@ typedef struct
 
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	FLAG_ISDIR				(1<<0) /* Show column: isdir */
-#define	FLAG_METADATA			(1<<1) /* Show columns: mtime, size */
-#define	FLAG_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
-#define	FLAG_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
-#define	FLAG_SKIP_HIDDEN		(1<<4) /* Do not show anything begining with . */
-#define	FLAG_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	FLAG_METADATA			(1<<0) /* Show columns: mtime, size */
+#define	FLAG_MISSING_OK			(1<<1) /* Ignore ENOENT if the toplevel dir is missing */
+#define	FLAG_SKIP_DOT_DIRS		(1<<2) /* Do not show . or .. */
+#define	FLAG_SKIP_HIDDEN		(1<<3) /* Do not show anything begining with . */
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -482,11 +480,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	struct dirent *de;
 	directory_fctx *fctx;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&FLAG_ISDIR) || (flags&FLAG_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&FLAG_ISDIR) || !(flags&FLAG_SKIP_DIRS));
-
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
 	{
@@ -517,8 +510,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		fctx = palloc(sizeof(directory_fctx));
 
-		tupdesc = CreateTemplateTupleDesc((flags&FLAG_ISDIR) ? 4 :
-				(flags&FLAG_METADATA) ? 3 : 1);
+		tupdesc = CreateTemplateTupleDesc((flags&FLAG_METADATA) ? 4 : 1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
 						   TEXTOID, -1, 0);
 		if (flags&FLAG_METADATA)
@@ -527,9 +519,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 							   INT8OID, -1, 0);
 			TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
 							   TIMESTAMPTZOID, -1, 0);
-			if (flags&FLAG_ISDIR)
-				TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
-						BOOLOID, -1, 0);
+			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+								BOOLOID, -1, 0);
 		}
 
 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
@@ -584,10 +575,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 					 errmsg("could not stat file \"%s\": %m", path)));
 
 		if (S_ISDIR(attrib.st_mode))
-		{
-			if (flags&FLAG_SKIP_DIRS)
-				continue;
-		}
+			; /* Do nothing, fall through */
 		else if (!S_ISREG(attrib.st_mode))
 			continue;
 
@@ -596,8 +584,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			values[0] = CStringGetTextDatum(de->d_name);
 			values[1] = Int64GetDatum((int64) attrib.st_size);
 			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & FLAG_ISDIR)
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
 		} else
 			SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
 
@@ -616,7 +603,7 @@ Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, Log_directory,
-			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
@@ -624,7 +611,7 @@ Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR,
-			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
+			FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /*
@@ -643,7 +630,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_METADATA|FLAG_ISDIR);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /*
@@ -673,7 +660,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /*
@@ -684,5 +671,5 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 {
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
-	return pg_ls_dir_files(fcinfo, dirname, FLAG_METADATA|FLAG_ISDIR);
+	return pg_ls_dir_files(fcinfo, dirname, FLAG_METADATA);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cc2c6f6571..0e5a570285 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6149,7 +6149,7 @@
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
   proallargtypes => '{text,text,int8,timestamptz,bool}',
   proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false, true) UNION ALL SELECT x.name||'/'||a.name, a.size, a.modification, a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false, true)a WHERE x.isdir) SELECT * FROM x" },
+  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false) UNION ALL SELECT x.name||'/'||a.name, a.size, a.modification, a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false)a WHERE x.isdir) SELECT * FROM x" },
 
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
@@ -10725,18 +10725,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -10751,9 +10751,9 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
-  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
-- 
2.17.0

  [text/x-diff] v9-0011-pg_ls_-dir-to-return-all-the-metadata-from-pg_sta.patch (15.4K, ../../20200310183037.GA29065@telsasoft.com/12-v9-0011-pg_ls_-dir-to-return-all-the-metadata-from-pg_sta.patch)
  download | inline diff:
From e8960ff73967e5ca855ee8f392e6a7711e2ee64c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v9 11/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

Need catversion bump
---
 doc/src/sgml/func.sgml                    | 30 +++++++++------------
 src/backend/utils/adt/genfile.c           | 32 ++++++++++++++++-------
 src/include/catalog/pg_proc.dat           | 30 ++++++++++-----------
 src/test/regress/output/tablespace.source |  8 +++---
 4 files changed, 55 insertions(+), 45 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d0b782d803..ae6c004a6d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21348,8 +21348,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21360,8 +21359,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21372,9 +21370,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21386,8 +21383,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -21463,8 +21459,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -21475,8 +21471,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21488,8 +21484,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21502,8 +21498,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 387114d4ee..1fe7e0181d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -510,16 +510,22 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		fctx = palloc(sizeof(directory_fctx));
 
-		tupdesc = CreateTemplateTupleDesc((flags&FLAG_METADATA) ? 4 : 1);
+		tupdesc = CreateTemplateTupleDesc((flags&FLAG_METADATA) ? 7 : 1);
 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
 						   TEXTOID, -1, 0);
 		if (flags&FLAG_METADATA)
 		{
 			TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
 							   INT8OID, -1, 0);
-			TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
+			TupleDescInitEntry(tupdesc, (AttrNumber) 3,
+							   "access", TIMESTAMPTZOID, -1, 0);
+			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "modification",
 							   TIMESTAMPTZOID, -1, 0);
-			TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+			TupleDescInitEntry(tupdesc, (AttrNumber) 5,
+							   "change", TIMESTAMPTZOID, -1, 0);
+			TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+							   "creation", TIMESTAMPTZOID, -1, 0);
+			TupleDescInitEntry(tupdesc, (AttrNumber) 7, "isdir",
 								BOOLOID, -1, 0);
 		}
 
@@ -551,8 +557,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 		HeapTuple	tuple;
@@ -579,17 +585,25 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		else if (!S_ISREG(attrib.st_mode))
 			continue;
 
+		memset(nulls, 0, sizeof(nulls));
 		if (flags & FLAG_METADATA)
 		{
 			values[0] = CStringGetTextDatum(de->d_name);
 			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_atime));
+			values[3] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+			values[4] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+			nulls[5] = true;
+#else
+			nulls[4] = true;
+			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+#endif
+			values[6] = BoolGetDatum(S_ISDIR(attrib.st_mode));
 		} else
 			SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
 
-		memset(nulls, 0, sizeof(nulls));
-
 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
 		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0e5a570285..b571666a79 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6147,9 +6147,9 @@
 { oid => '8511', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false) UNION ALL SELECT x.name||'/'||a.name, a.size, a.modification, a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false)a WHERE x.isdir) SELECT * FROM x" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false) UNION ALL SELECT x.name||'/'||a.name, a.size,a.access,a.modification,a.change,a.creation,a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false)a WHERE x.isdir) SELECT * FROM x" },
 
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
@@ -10725,35 +10725,35 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index dacaafcae6..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-13 13:12         ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-13 13:12 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

@cfbot: rebased onto 085b6b6679e73b9b386f209b4d625c7bc60597c0

The merge conflict presents another opportunity to solicit comments on the new
approach.  Rather than making "recurse into tmpdir" the end goal:

  - add a function to show metadata of an arbitrary dir;
  - add isdir arguments to pg_ls_* functions (including pg_ls_tmpdir but not
    pg_ls_dir).
  - maybe add pg_ls_dir_recurse, which satisfies the original need;
  - retire pg_ls_dir (does this work with tuplestore?)
  - profit

The alternative seems to be to go back to Alvaro's earlier proposal:
 - not only add "isdir", but also recurse;

I think I would insist on adding a general function to recurse into any dir.
And *optionally* change ps_ls_* to recurse (either by accepting an argument, or
by making that a separate patch to debate).  tuplestore is certainly better
than keeping a stack/List of DIRs for this.

On Tue, Mar 10, 2020 at 01:30:37PM -0500, Justin Pryzby wrote:
> I took a step back, and I wondered whether we should add a generic function for
> listing a dir with metadata, possibly instead of changing the existing
> functions.  Then one could do pg_ls_dir_metadata('pg_wal',false,false);
> 
> Since pg8.1, we have pg_ls_dir() to show a list of files.  Since pg10, we've
> had pg_ls_logdir and pg_ls_waldir, which show not only file names but also
> (some) metadata (size, mtime).  And since pg12, we've had pg_ls_tmpfile and
> pg_ls_archive_statusdir, which also show metadata.
> 
> ...but there's no a function which lists the metadata of an directory other
> than tmp, wal, log.
> 
> One can do this:
> |SELECT b.*, c.* FROM (SELECT 'base' a)a, LATERAL (SELECT a||'/'||pg_ls_dir(a.a)b)b, pg_stat_file(b)c;
> ..but that's not as helpful as allowing:
> |SELECT * FROM pg_ls_dir_metadata('.',true,true);
> 
> There's also no function which recurses into an arbitrary directory, so it
> seems shortsighted to provide a function to recursively list a tmpdir.
> 
> Also, since pg_ls_dir_metadata indicates whether the path is a dir, one can
> write a SQL function to show the dir recursively.  It'd be trivial to plug in
> wal/log/tmp (it seems like tmpdirs of other tablespace's are not entirely
> trivial).
> |SELECT * FROM pg_ls_dir_recurse('base/pgsql_tmp');
> 
> Also, on a neighboring thread[1], Tom indicated that the pg_ls_* functions
> should enumerate all files during the initial call, which sounds like a bad
> idea when recursively showing directories.  If we add a function recursing into
> a directory, we'd need to discuss all the flags to expose to it, like recurse,
> ignore_errors, one_filesystem?, show_dotfiles (and eventually bikeshed all the
> rest of the flags in find(1)).
> 
> My initial patch [2] changed ls_tmpdir to show metadata columns including
> is_dir, but not decend.  It's pretty unfortunate if a function called
> pg_ls_tmpdir hides shared filesets, so maybe it really is best to change that
> (it's new in v12).
> 
> I'm interested to in feedback on the alternative approach, as attached.  The
> final patch to include all the rest of columns shown by pg_stat_file() is more
> of an idea/proposal and not sure if it'll be desirable.  But pg_ls_tmpdir() is
> essentially the same as my v1 patch.
> 
> This is intended to be mostly independent of any fix to the WARNING I reported
> [1].  Since my patch collapses pg_ls_dir into pg_ls_dir_files, we'd only need
> to fix one place.  I'm planning to eventually look into Tom's suggestion of
> returning tuplestore to fix that, and maybe rebase this patchset on top of
> that.
> 
> -- 
> Justin
> 
> [1] https://www.postgresql.org/message-id/flat/20200308173103.GC1357%40telsasoft.com
> [2] https://www.postgresql.org/message-id/20191214224735.GA28433%40telsasoft.com

Attachments:

  [text/x-diff] v10-0001-Document-historic-behavior-about-hiding-director.patch (1.7K, ../../20200313131232.GO29065@telsasoft.com/2-v10-0001-Document-historic-behavior-about-hiding-director.patch)
  download | inline diff:
From 950eed8812a167a12da49553f7e3a2d1438d779b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 16:50:07 -0600
Subject: [PATCH v10 1/9] Document historic behavior about hiding directories
 and special files

Should backpatch to v10: tmpdir, waldir and archive_statusdir
---
 doc/src/sgml/func.sgml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..4c0ea5ab3f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21450,6 +21450,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     (mtime) of each file in the log directory. By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21461,6 +21462,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21473,6 +21475,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v10-0002-Document-historic-behavior-about-hiding-director.patch (904B, ../../20200313131232.GO29065@telsasoft.com/3-v10-0002-Document-historic-behavior-about-hiding-director.patch)
  download | inline diff:
From a04d046211dbddfe1cb96fd794ed9ef269e2415f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:12:04 -0600
Subject: [PATCH v10 2/9] Document historic behavior about hiding directories
 and special files

Should backpatch to v12: tmpdir
---
 doc/src/sgml/func.sgml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c0ea5ab3f..fc4d7f0f78 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21489,6 +21489,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v10-0003-Add-tests-exercizing-pg_ls_tmpdir.patch (4.3K, ../../20200313131232.GO29065@telsasoft.com/4-v10-0003-Add-tests-exercizing-pg_ls_tmpdir.patch)
  download | inline diff:
From 11100ba3d74051825972f80f5d5f93237762343f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v10 3/9] Add tests exercizing pg_ls_tmpdir..

..and its backing function pg_ls_dir_files
---
 src/test/regress/expected/misc_functions.out | 7 +++++++
 src/test/regress/input/tablespace.source     | 5 +++++
 src/test/regress/output/tablespace.source    | 8 ++++++++
 src/test/regress/sql/misc_functions.sql      | 4 ++++
 4 files changed, 24 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 5eccc97bdb..3b1e62834f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -179,6 +179,13 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
  t
 (1 row)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 02d1fc238d..034bbd8d11 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -50,6 +50,10 @@ select (pg_ls_waldir()).size = :segsize as ok limit 1;
 
 select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v10-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (9.9K, ../../20200313131232.GO29065@telsasoft.com/5-v10-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 969533c27663713d87f6ff2eb0a3c32b13f5c146 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 17:15:02 -0500
Subject: [PATCH v10 4/9] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 src/backend/utils/adt/genfile.c | 184 +++++++++++++++++---------------
 src/include/catalog/pg_proc.dat |   6 ++
 2 files changed, 106 insertions(+), 84 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bcf9bd1b97..4f437573ab 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,13 +36,15 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
-typedef struct
-{
-	char	   *location;
-	DIR		   *dirdesc;
-	bool		include_dot_dirs;
-} directory_fctx;
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
+#define	FLAG_ISDIR				(1<<0) /* Show column: isdir */
+#define	FLAG_METADATA			(1<<1) /* Show columns: mtime, size */
+#define	FLAG_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	FLAG_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	FLAG_SKIP_HIDDEN		(1<<4) /* Do not show anything begining with . */
+#define	FLAG_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	FLAG_SKIP_SPECIAL		(1<<6) /* Do not show special file types */
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -447,67 +449,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	FuncCallContext *funcctx;
-	struct dirent *de;
-	directory_fctx *fctx;
-	MemoryContext oldcontext;
-
-	if (SRF_IS_FIRSTCALL())
-	{
-		bool		missing_ok = false;
-		bool		include_dot_dirs = false;
-
-		/* check the optional arguments */
-		if (PG_NARGS() == 3)
-		{
-			if (!PG_ARGISNULL(1))
-				missing_ok = PG_GETARG_BOOL(1);
-			if (!PG_ARGISNULL(2))
-				include_dot_dirs = PG_GETARG_BOOL(2);
-		}
-
-		funcctx = SRF_FIRSTCALL_INIT();
-		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
-
-		fctx = palloc(sizeof(directory_fctx));
-		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-		fctx->include_dot_dirs = include_dot_dirs;
-		fctx->dirdesc = AllocateDir(fctx->location);
-
-		if (!fctx->dirdesc)
-		{
-			if (missing_ok && errno == ENOENT)
-			{
-				MemoryContextSwitchTo(oldcontext);
-				SRF_RETURN_DONE(funcctx);
-			}
-			else
-				ereport(ERROR,
-						(errcode_for_file_access(),
-						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
-		}
-		funcctx->user_fctx = fctx;
-		MemoryContextSwitchTo(oldcontext);
-	}
-
-	funcctx = SRF_PERCALL_SETUP();
-	fctx = (directory_fctx *) funcctx->user_fctx;
-
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
-	{
-		if (!fctx->include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
-	}
-
-	FreeDir(fctx->dirdesc);
-
-	SRF_RETURN_DONE(funcctx);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, FLAG_SKIP_DOT_DIRS);
 }
 
 /*
@@ -520,7 +464,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, FLAG_SKIP_DOT_DIRS);
 }
 
 /*
@@ -530,7 +476,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -539,6 +485,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&FLAG_ISDIR) || (flags&FLAG_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&FLAG_ISDIR) || !(flags&FLAG_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= FLAG_MISSING_OK;
+			else
+				flags &= ~FLAG_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~FLAG_SKIP_DOT_DIRS;
+			else
+				flags |= FLAG_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -554,8 +526,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags&FLAG_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	} else {
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -574,7 +556,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags&FLAG_MISSING_OK && errno == ENOENT)
 		{
 			tuplestore_donestoring(tupstore);
 			return (Datum) 0;
@@ -584,13 +566,19 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
+		if (flags&FLAG_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
 		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		if (flags&FLAG_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -600,13 +588,26 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags&FLAG_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags&FLAG_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & FLAG_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & FLAG_ISDIR)
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +622,16 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory,
+			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR,
+			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /*
@@ -646,7 +649,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /*
@@ -675,5 +679,17 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname, FLAG_METADATA|FLAG_SKIP_SPECIAL|FLAG_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
-- 
2.17.0

  [text/x-diff] v10-0005-pg_ls_tmpdir-to-show-isdir-argument.patch (7.6K, ../../20200313131232.GO29065@telsasoft.com/6-v10-0005-pg_ls_tmpdir-to-show-isdir-argument.patch)
  download | inline diff:
From 48add2f8cb7697de9735dda714a470708cf8ff44 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v10 5/9] pg_ls_tmpdir to show "isdir" argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Note that "dir_ok" means both: do not skip dirs, *and* show "isdir" column.
The other functions both skip dirs and only show 3 columns, and ls_tmpdir is
changing to both show dirs and show 4 columns.  It wouldn't make sense to skip
dirs and then show a 4th column always false.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 22 +++++++++++---------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 +++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..7c73d3b82a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21382,12 +21382,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21482,14 +21482,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4f437573ab..5e25e31dff 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -470,7 +470,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -650,7 +650,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_ISDIR|FLAG_METADATA);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0a1859f709..7789d029ea 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10733,13 +10733,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 3b1e62834f..94b562dcb6 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -182,8 +182,8 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v10-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (12.8K, ../../20200313131232.GO29065@telsasoft.com/7-v10-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 355eddbb6dcadc994b8ce65744e7aab1b96281e6 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v10 6/9] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

..FLAG_ISDIR is collapsed into FLAG_METADATA.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 39 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 35 ++++++------------
 src/include/catalog/pg_proc.dat              | 19 +++++-----
 src/test/regress/expected/misc_functions.out |  4 +-
 4 files changed, 46 insertions(+), 51 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c73d3b82a..3d8f7dff96 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21348,8 +21348,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21359,8 +21360,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21370,8 +21372,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21446,36 +21449,38 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5e25e31dff..3ea8c5683e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -38,13 +38,11 @@
 
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	FLAG_ISDIR				(1<<0) /* Show column: isdir */
-#define	FLAG_METADATA			(1<<1) /* Show columns: mtime, size */
-#define	FLAG_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
-#define	FLAG_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
-#define	FLAG_SKIP_HIDDEN		(1<<4) /* Do not show anything begining with . */
-#define	FLAG_SKIP_DIRS			(1<<5) /* Do not show directories */
-#define	FLAG_SKIP_SPECIAL		(1<<6) /* Do not show special file types */
+#define	FLAG_METADATA			(1<<0) /* Show columns: mtime, size */
+#define	FLAG_MISSING_OK			(1<<1) /* Ignore ENOENT if the toplevel dir is missing */
+#define	FLAG_SKIP_DOT_DIRS		(1<<2) /* Do not show . or .. */
+#define	FLAG_SKIP_HIDDEN		(1<<3) /* Do not show anything begining with . */
+#define	FLAG_SKIP_SPECIAL		(1<<4) /* Do not show special file types */
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -487,11 +485,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&FLAG_ISDIR) || (flags&FLAG_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&FLAG_ISDIR) || !(flags&FLAG_SKIP_DIRS));
-
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
 	{
@@ -589,10 +582,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 					 errmsg("could not stat file \"%s\": %m", path)));
 
 		if (S_ISDIR(attrib.st_mode))
-		{
-			if (flags&FLAG_SKIP_DIRS)
-				continue;
-		}
+			; /* Do nothing, fall through */
 		else if (!S_ISREG(attrib.st_mode))
 		{
 			if (flags&FLAG_SKIP_SPECIAL)
@@ -604,8 +594,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		{
 			values[1] = Int64GetDatum((int64) attrib.st_size);
 			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & FLAG_ISDIR)
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
 		}
 
 		memset(nulls, 0, sizeof(nulls));
@@ -623,7 +612,7 @@ Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, Log_directory,
-			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+			FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
@@ -631,7 +620,7 @@ Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR,
-			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+			FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /*
@@ -650,7 +639,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_ISDIR|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /*
@@ -680,7 +669,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /*
@@ -691,5 +680,5 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 {
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
-	return pg_ls_dir_files(fcinfo, dirname, FLAG_METADATA|FLAG_SKIP_SPECIAL|FLAG_ISDIR);
+	return pg_ls_dir_files(fcinfo, dirname, FLAG_METADATA|FLAG_SKIP_SPECIAL);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7789d029ea..b4c3d15495 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6143,6 +6143,7 @@
   proname => 'pg_ls_dir', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'text', proargtypes => 'text bool bool',
   prosrc => 'pg_ls_dir' },
+
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
   proargtypes => 'float8', prosrc => 'pg_sleep' },
@@ -10717,18 +10718,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -10743,9 +10744,9 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
-  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 94b562dcb6..d11b8669c3 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v10-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (2.7K, ../../20200313131232.GO29065@telsasoft.com/8-v10-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From fc5adf721c5a89c99a0d5caffcb35cf13ab1cf7f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v10 7/9] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

TODO:
src/backend/catalog/system_views.sql:REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;

Need catversion bumped ?
---
 doc/src/sgml/func.sgml          | 13 +++++++++++++
 src/include/catalog/pg_proc.dat |  7 +++++++
 2 files changed, 20 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3d8f7dff96..d0b782d803 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21445,6 +21445,19 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    <parameter>missing_ok</parameter> ???
+    To recusively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp')dir  FROM pg_tablespace b, pg_control_system() pcs, format('/PG_%s_%s', left(current_setting('server_version_num'), 2), catalog_version_no) suffix)dir, pg_ls_dir_recurse(dir)a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b4c3d15495..75423db22e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6144,6 +6144,13 @@
   provolatile => 'v', prorettype => 'text', proargtypes => 'text bool bool',
   prosrc => 'pg_ls_dir' },
 
+{ oid => '8511', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false, true) UNION ALL SELECT x.name||'/'||a.name, a.size, a.modification, a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false, true)a WHERE x.isdir) SELECT * FROM x" },
+
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
   proargtypes => 'float8', prosrc => 'pg_sleep' },
-- 
2.17.0

  [text/x-diff] v10-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.0K, ../../20200313131232.GO29065@telsasoft.com/9-v10-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From f99592c990ebcad27b0cbe1ef6c75f8c8696fad0 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v10 8/9] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3ea8c5683e..19e0b8a82c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -612,7 +612,7 @@ Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, Log_directory,
-			FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..dacaafcae6 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v10-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (15.3K, ../../20200313131232.GO29065@telsasoft.com/10-v10-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 63564d624258925fc71bad9796bc3bcff6ceb5ec Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v10 9/9] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 30 +++++++++-----------
 src/backend/utils/adt/genfile.c              | 17 ++++++++---
 src/include/catalog/pg_proc.dat              | 30 ++++++++++----------
 src/test/regress/expected/misc_functions.out |  8 +++---
 src/test/regress/output/tablespace.source    |  8 +++---
 5 files changed, 49 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d0b782d803..ae6c004a6d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21348,8 +21348,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21360,8 +21359,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21372,9 +21370,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21386,8 +21383,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -21463,8 +21459,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -21475,8 +21471,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21488,8 +21484,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21502,8 +21498,8 @@ SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 19e0b8a82c..fb6cdc07a7 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -559,8 +559,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -593,8 +593,17 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		if (flags & FLAG_METADATA)
 		{
 			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_atime));
+			values[3] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+			values[4] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+			nulls[5] = true;
+#else
+			nulls[4] = true;
+			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+#endif
+			values[6] = BoolGetDatum(S_ISDIR(attrib.st_mode));
 		}
 
 		memset(nulls, 0, sizeof(nulls));
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 75423db22e..6c11aa940e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6147,9 +6147,9 @@
 { oid => '8511', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false, true) UNION ALL SELECT x.name||'/'||a.name, a.size, a.modification, a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false, true)a WHERE x.isdir) SELECT * FROM x" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "WITH RECURSIVE x AS (SELECT * FROM pg_ls_dir_metadata(dirname, true, false) UNION ALL SELECT x.name||'/'||a.name, a.size, a.access, a.modification,a.change,a.creation,a.isdir FROM x, pg_ls_dir_metadata(dirname||'/'||x.name, true, false)a WHERE x.isdir) SELECT * FROM x" },
 
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
@@ -10725,35 +10725,35 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d11b8669c3..ea7056cd5c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -182,8 +182,8 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index dacaafcae6..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-15 17:15           ` Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Fabien COELHO @ 2020-03-15 17:15 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>


Hello Justin,

Some feedback on v10:

All patches apply cleanly, one on top of the previous. I really wish there 
would be less than 9 patches…

* v10.1 doc change: ok

* v10.2 doc change: ok, not sure why it is not merged with previous

* v10.3 test add: could be merge with both previous

Tests seem a little contrived. I'm wondering whether something more 
straightforward could be proposed. For instance, once the tablespace is 
just created but not used yet, probably we do know that the tmp file 
exists and is empty?

* v10.4 at least, some code!

Compiles, make check ok.

pg_ls_dir_files: I'm fine with the flag approach given the number of 
switches and the internal nature of the function.

I'm not sure of the "FLAG_" prefix which seems too generic, even if it is 
local. I'd suggest "LS_DIR_*", maybe, as a more specific prefix.

ISTM that Pg style requires spaces around operators. I'd suggest some 
parenthesis would help as well, eg: "flags&FLAG_MISSING_OK" -> "(flags & 
FLAG_MISSING_OK)" and other instances.

About:

  if (S_ISDIR(attrib.st_mode)) {
    if (flags&FLAG_SKIP_DIRS)
      continue;
   }

and similars, why not the simpler:

  if (S_ISDIR(attrib.st_mode) && (flags & FLAG_SKIP_DIRS))
     continue;

Especially that it is done like that in previous cases.

Maybe I'd create defines for long and common flag specs, eg:

  #define ..._LS_SIMPLE (FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA)

No attempt at recursing.

I'm not sure about these asserts:

        /* isdir depends on metadata */
        Assert(!(flags&FLAG_ISDIR) || (flags&FLAG_METADATA));

Hmmm. Why?

        /* Unreasonable to show isdir and skip dirs */
        Assert(!(flags&FLAG_ISDIR) || !(flags&FLAG_SKIP_DIRS));

Hmmm. Why would I prevent that, even if it has little sense, it should 
work. I do not see having false on the isdir column as an actual issue.

* v10.5 add is_dir column, a few tests & doc.

Ok.

* v10.6 behavior change for existing functions, always show isdir column,
and removal of IS_DIR flag.

I'm unsure why the features are removed, some use case may benefit from 
the more complete function?

Maybe flags defs should not be changed anyway?

I do not like much the "if (...) /* empty */;" code. Maybe it could be 
caught more cleanly later in the conditional structure.

* v10.7 adds "pg_ls_dir_recurse" function

Using sql recurse to possibly to implement the feature is pretty elegant
and limits open directories to one at a time, which is pretty neat.

Doc looks incomplete and the example is very contrived and badly indented.

The function definition does not follow the style around: uppercase 
whereas all others are lowercase, "" instead of '', no "as"…

I do not understand why oid 8511 is given to the new function.

I do not understand why UNION ALL and not UNION.

I would have put the definition after "pg_ls_dir_metadata" definition.

pg_ls_dir_metadata seems defined as (text,bool,bool) but called as 
(text,bool,bool,bool).

Maybe a better alias could be given instead of x?

There are no tests for the new function. I'm not sure it would work.

* v10.8 change flags & add test on pg_ls_logdir().

I'm unsure why it is done at this stage.

* v10.9 change some ls functions and fix patch 10.7 issue

I'm unsure why it is done at this stage. "make check" ok.

-- 
Fabien.

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-03-15 21:27             ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-15 21:27 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Sun, Mar 15, 2020 at 06:15:02PM +0100, Fabien COELHO wrote:
> Some feedback on v10:

Thanks for looking.  I'm hoping to hear from Alvaro what he thinks of this
approach (all functions to show isdir, rather than one function which lists
recursively).

> All patches apply cleanly, one on top of the previous. I really wish there
> would be less than 9 patches…

I kept them separate to allow the earlier patches to be applied.
And intended to make easier to review, even if it's more work for me..

If you mean that it's a pain to apply 9 patches, I will suggest to use:
|git am -3 ./mailbox
where ./mailbox is either a copy of the mail you received, or retrieved from
the web interface.

To test that each one works (compiles, passes tests, etc), I use git rebase -i
HEAD~11 and "e"edit the target (set of) patches.

> * v10.1 doc change: ok
> 
> * v10.2 doc change: ok, not sure why it is not merged with previous

As I mentioned, separate since I'm proposing that they're backpatched to
different releases.  Those could be applied now (and Tom already applied a
patch identical to 0001 in a prior patchset).

> * v10.3 test add: could be merge with both previous

> Tests seem a little contrived. I'm wondering whether something more
> straightforward could be proposed. For instance, once the tablespace is just
> created but not used yet, probably we do know that the tmp file exists and
> is empty?

The tmpdir *doesn't* exist until someone creates tmpfiles there.
As it mentions:
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet

> * v10.4 at least, some code!
> I'm not sure of the "FLAG_" prefix which seems too generic, even if it is
> local. I'd suggest "LS_DIR_*", maybe, as a more specific prefix.

Done.

> ISTM that Pg style requires spaces around operators. I'd suggest some
> parenthesis would help as well, eg: "flags&FLAG_MISSING_OK" -> "(flags &
> FLAG_MISSING_OK)" and other instances.

Partially took your suggestion.

> About:
> 
>  if (S_ISDIR(attrib.st_mode)) {
>    if (flags&FLAG_SKIP_DIRS)
>      continue;
>   }
> 
> and similars, why not the simpler:
> 
>  if (S_ISDIR(attrib.st_mode) && (flags & FLAG_SKIP_DIRS))
>     continue;

That's not the same - if SKIP_DIRS isn't set, your way would fail that test for
dirs, and then hit the !ISREG test, and skip them anyway.
|else if (!S_ISREG(attrib.st_mode))
|	continue

> Maybe I'd create defines for long and common flag specs, eg:
> 
>  #define ..._LS_SIMPLE (FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA)

Done

> I'm not sure about these asserts:
> 
>        /* isdir depends on metadata */
>        Assert(!(flags&FLAG_ISDIR) || (flags&FLAG_METADATA));
> 
> Hmmm. Why?

It's not supported to show isdir without showing metadata (because that case
isn't needed to support the old and the new behaviors).

+               if (flags & FLAG_METADATA)
+               {
+                       values[1] = Int64GetDatum((int64) attrib.st_size);
+                       values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+                       if (flags & FLAG_ISDIR)
+                               values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+               }

>        /* Unreasonable to show isdir and skip dirs */
>        Assert(!(flags&FLAG_ISDIR) || !(flags&FLAG_SKIP_DIRS));
> 
> Hmmm. Why would I prevent that, even if it has little sense, it should work.
> I do not see having false on the isdir column as an actual issue.

It's unimportant, but testing for intended use of flags during development.

> * v10.6 behavior change for existing functions, always show isdir column,
> and removal of IS_DIR flag.
> 
> I'm unsure why the features are removed, some use case may benefit from the
> more complete function?
> Maybe flags defs should not be changed anyway?

Maybe.  I put them back...but it means they're not being exercized by any
*used* case.

> I do not like much the "if (...) /* empty */;" code. Maybe it could be
> caught more cleanly later in the conditional structure.

This went away when I put back the SKIP_DIRS flag.

> * v10.7 adds "pg_ls_dir_recurse" function

> Doc looks incomplete and the example is very contrived and badly indented.

Why you think it's contrived?  Listing a tmpdir recursively is the initial
motivation of this patch.  Maybe you think I should list just the tmpdir for
one tablespace ?  Note that for temp_tablespaces parameter:

|When there is more than one name in the list, PostgreSQL chooses a random member of the list each time a temporary object is to be created; except that within a transaction, successively created temporary objects are placed in successive tablespaces from the list.

> The function definition does not follow the style around: uppercase whereas
> all others are lowercase, "" instead of '', no "as"…

I used "" because of this:
| x.name||'/'||a.name
I don't know if there's a better way to join paths in SQL, or if that suggests
this is a bad way to do it.

> I do not understand why oid 8511 is given to the new function.

I used: ./src/include/catalog/unused_oids (maybe not correctly).

> I do not understand why UNION ALL and not UNION.

In general, union ALL can avoid a "distinct" plan node, but it doesn't seem to
have any effect here.

> I would have put the definition after "pg_ls_dir_metadata" definition.

Done

> pg_ls_dir_metadata seems defined as (text,bool,bool) but called as
> (text,bool,bool,bool).

fixed, thanks.

> Maybe a better alias could be given instead of x?
> 
> There are no tests for the new function. I'm not sure it would work.

I added something which would've caught the issue with number of arguments.

> * v10.8 change flags & add test on pg_ls_logdir().
> 
> I'm unsure why it is done at this stage.

I think it makes sense to allow ls_logdir to succeed even if ./log doesn't
exist, since it isn't created by initdb or during postmaster start, and since
we already using MISSING_OK for tmpdir.

But a separate patch since we didn't previous discuss changing logdir.  

> * v10.9 change some ls functions and fix patch 10.7 issue
> I'm unsure why it is done at this stage. "make check" ok.

This is the last patch in the series, since I think it's least likely to be
agreed on.

-- 
Justin

Attachments:

  [text/x-diff] v11-0001-Document-historic-behavior-about-hiding-director.patch (1.7K, ../../20200315212729.GC26184@telsasoft.com/2-v11-0001-Document-historic-behavior-about-hiding-director.patch)
  download | inline diff:
From 1bb8e0efb4f14fa344cd5ee66c3138184a9fa9e2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 16:50:07 -0600
Subject: [PATCH v11 1/9] Document historic behavior about hiding directories
 and special files

Should backpatch to v10: tmpdir, waldir and archive_statusdir
---
 doc/src/sgml/func.sgml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..4c0ea5ab3f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21450,6 +21450,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     (mtime) of each file in the log directory. By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21461,6 +21462,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21473,6 +21475,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v11-0002-Document-historic-behavior-about-hiding-director.patch (904B, ../../20200315212729.GC26184@telsasoft.com/3-v11-0002-Document-historic-behavior-about-hiding-director.patch)
  download | inline diff:
From c8b89e104694645873b77f987d795269fc330bf7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:12:04 -0600
Subject: [PATCH v11 2/9] Document historic behavior about hiding directories
 and special files

Should backpatch to v12: tmpdir
---
 doc/src/sgml/func.sgml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c0ea5ab3f..fc4d7f0f78 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21489,6 +21489,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v11-0003-Add-tests-exercizing-pg_ls_tmpdir.patch (4.3K, ../../20200315212729.GC26184@telsasoft.com/4-v11-0003-Add-tests-exercizing-pg_ls_tmpdir.patch)
  download | inline diff:
From 96de8a92294eec8c33ecb2ef0dbf48add281b763 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v11 3/9] Add tests exercizing pg_ls_tmpdir..

..and its backing function pg_ls_dir_files
---
 src/test/regress/expected/misc_functions.out | 7 +++++++
 src/test/regress/input/tablespace.source     | 5 +++++
 src/test/regress/output/tablespace.source    | 8 ++++++++
 src/test/regress/sql/misc_functions.sql      | 4 ++++
 4 files changed, 24 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e217b678d7..9947d9ef9d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -180,6 +180,13 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
  t
 (1 row)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 1e11eb3554..656aad93e6 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -51,6 +51,10 @@ from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1;
 
 select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v11-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (11.6K, ../../20200315212729.GC26184@telsasoft.com/5-v11-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 3156f1c19e0389a0a23546971ca8715d378e0370 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 17:15:02 -0500
Subject: [PATCH v11 4/9] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml          |  17 +++
 src/backend/utils/adt/genfile.c | 190 ++++++++++++++++++--------------
 src/include/catalog/pg_proc.dat |   6 +
 3 files changed, 129 insertions(+), 84 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..dfea9f8061 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         List the contents of a directory.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+        For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_metadata</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_metadata</function> lists the files in the specified
+    directory along with the file's metadata.
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bcf9bd1b97..b105363903 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,14 +36,23 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
-typedef struct
-{
-	char	   *location;
-	DIR		   *dirdesc;
-	bool		include_dot_dirs;
-} directory_fctx;
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -447,67 +456,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	FuncCallContext *funcctx;
-	struct dirent *de;
-	directory_fctx *fctx;
-	MemoryContext oldcontext;
-
-	if (SRF_IS_FIRSTCALL())
-	{
-		bool		missing_ok = false;
-		bool		include_dot_dirs = false;
-
-		/* check the optional arguments */
-		if (PG_NARGS() == 3)
-		{
-			if (!PG_ARGISNULL(1))
-				missing_ok = PG_GETARG_BOOL(1);
-			if (!PG_ARGISNULL(2))
-				include_dot_dirs = PG_GETARG_BOOL(2);
-		}
-
-		funcctx = SRF_FIRSTCALL_INIT();
-		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
-
-		fctx = palloc(sizeof(directory_fctx));
-		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-		fctx->include_dot_dirs = include_dot_dirs;
-		fctx->dirdesc = AllocateDir(fctx->location);
-
-		if (!fctx->dirdesc)
-		{
-			if (missing_ok && errno == ENOENT)
-			{
-				MemoryContextSwitchTo(oldcontext);
-				SRF_RETURN_DONE(funcctx);
-			}
-			else
-				ereport(ERROR,
-						(errcode_for_file_access(),
-						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
-		}
-		funcctx->user_fctx = fctx;
-		MemoryContextSwitchTo(oldcontext);
-	}
-
-	funcctx = SRF_PERCALL_SETUP();
-	fctx = (directory_fctx *) funcctx->user_fctx;
-
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
-	{
-		if (!fctx->include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
-	}
-
-	FreeDir(fctx->dirdesc);
-
-	SRF_RETURN_DONE(funcctx);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -520,7 +471,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -530,7 +483,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -539,6 +492,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -554,8 +533,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	} else {
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -574,7 +563,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
 		{
 			tuplestore_donestoring(tupstore);
 			return (Datum) 0;
@@ -584,13 +573,19 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
 		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -600,13 +595,26 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +629,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -646,7 +654,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
 }
 
 /*
@@ -675,5 +684,18 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
-- 
2.17.0

  [text/x-diff] v11-0005-pg_ls_tmpdir-to-show-isdir-argument.patch (7.6K, ../../20200315212729.GC26184@telsasoft.com/6-v11-0005-pg_ls_tmpdir-to-show-isdir-argument.patch)
  download | inline diff:
From ede11bc6af39a7234c31508bf981ee193dc0e0cb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v11 5/9] pg_ls_tmpdir to show "isdir" argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Note that "dir_ok" means both: do not skip dirs, *and* show "isdir" column.
The other functions both skip dirs and only show 3 columns, and ls_tmpdir is
changing to both show dirs and show 4 columns.  It wouldn't make sense to skip
dirs and then show a 4th column always false.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 22 +++++++++++---------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 +++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dfea9f8061..82891e2d64 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21391,12 +21391,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21499,14 +21499,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index b105363903..99a540baf2 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -477,7 +477,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -655,7 +655,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR|LS_DIR_METADATA|LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0a1859f709..7789d029ea 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10733,13 +10733,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 9947d9ef9d..b0cf145dc5 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -183,8 +183,8 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v11-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (10.6K, ../../20200315212729.GC26184@telsasoft.com/7-v11-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 3398c7eaeb2c3ee2be8d80a726bea6d9a191e470 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v11 6/9] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

XXX: ..FLAG_ISDIR is collapsed into FLAG_METADATA.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 39 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 ++++--
 src/include/catalog/pg_proc.dat              | 19 +++++-----
 src/test/regress/expected/misc_functions.out |  4 +-
 4 files changed, 41 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 82891e2d64..e4ba3fee40 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21357,8 +21357,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21368,8 +21369,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21379,8 +21381,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21463,36 +21466,38 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 99a540baf2..9c2feb0986 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -53,6 +53,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for showing ... */
+#define LS_DIR_MODERN				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -629,14 +632,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_MODERN);
 }
 
 /*
@@ -655,7 +658,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR|LS_DIR_METADATA|LS_DIR_MISSING_OK);
+			LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /*
@@ -685,7 +688,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+			LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7789d029ea..b4c3d15495 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6143,6 +6143,7 @@
   proname => 'pg_ls_dir', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'text', proargtypes => 'text bool bool',
   prosrc => 'pg_ls_dir' },
+
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
   proargtypes => 'float8', prosrc => 'pg_sleep' },
@@ -10717,18 +10718,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -10743,9 +10744,9 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
-  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b0cf145dc5..448901d841 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v11-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (5.3K, ../../20200315212729.GC26184@telsasoft.com/8-v11-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From ed17a9fb0975d35f36f6925e480029c461f03724 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v11 7/9] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

TODO:
src/backend/catalog/system_views.sql:REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 24 ++++++++++++++++++++
 src/include/catalog/pg_proc.dat              |  6 +++++
 src/test/regress/expected/misc_functions.out | 14 ++++++++++++
 src/test/regress/sql/misc_functions.sql      |  6 +++++
 4 files changed, 50 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e4ba3fee40..f3422808d9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21351,6 +21351,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_recurse(<parameter>dirname</parameter> <type>text</type>)</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+         Call pg_ls_dir_metadata to recursively list the files in the specified directory, along with each file's metadata.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21462,6 +21471,21 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     directory along with the file's metadata.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+format('/PG_%s_%s', left(current_setting('server_version_num'), 2), catalog_version_no) AS suffix) AS dir,
+pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b4c3d15495..667ea5721e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10748,6 +10748,12 @@
   proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
   proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8511', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 448901d841..82d967a62d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -187,6 +187,20 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 656aad93e6..2cfdac386d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -55,6 +55,12 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v11-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.0K, ../../20200315212729.GC26184@telsasoft.com/9-v11-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From e5b3bbf8fcf2a89ad5e273a939ef7257c0323fb2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v11 8/9] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9c2feb0986..2d4f561ae0 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v11-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (15.6K, ../../20200315212729.GC26184@telsasoft.com/10-v11-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From e03b906cc3e00989a3ed8db93ea655b57b71b3ce Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v11 9/9] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 30 +++++++++-----------
 src/backend/utils/adt/genfile.c              | 18 ++++++++----
 src/include/catalog/pg_proc.dat              | 30 ++++++++++----------
 src/test/regress/expected/misc_functions.out | 12 ++++----
 src/test/regress/output/tablespace.source    |  8 +++---
 5 files changed, 51 insertions(+), 47 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f3422808d9..7f7d81da6e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21366,8 +21366,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21378,8 +21377,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21390,9 +21388,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21404,8 +21401,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -21491,8 +21487,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -21503,8 +21499,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21516,8 +21512,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21530,8 +21526,8 @@ pg_ls_dir_recurse(dir) AS a;
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 2d4f561ae0..7459371e48 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -576,8 +576,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -613,9 +613,17 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		if (flags & LS_DIR_METADATA)
 		{
 			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_atime));
+			values[3] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+			values[4] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+			nulls[5] = true;
+#else
+			nulls[4] = true;
+			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+#endif
+			values[6] = BoolGetDatum(S_ISDIR(attrib.st_mode));
 		}
 
 		memset(nulls, 0, sizeof(nulls));
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 667ea5721e..b74ed64b71 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10718,42 +10718,42 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8511', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification,a.change,a.creation,a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 82d967a62d..3544977a51 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -183,8 +183,8 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -197,8 +197,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-16 15:20               ` Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 15:41                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 2 replies; 70+ messages in thread

From: Fabien COELHO @ 2020-03-16 15:20 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>


About v11, ISTM that the recursive function should check for symbolic 
links and possibly avoid them:

  sh> cd data/base
  sh> ln -s .. foo

  psql> SELECT * FROM pg_ls_dir_recurse('.');
  ERROR:  could not stat file "./base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo": Too many levels of symbolic links
  CONTEXT:  SQL function "pg_ls_dir_recurse" statement 1

This probably means using lstat instead of (in supplement to?) stat, and 
probably tell if something is a link, and if so not recurse in them.

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-03-16 15:41                 ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 18:21                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-16 15:41 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Mon, Mar 16, 2020 at 04:20:21PM +0100, Fabien COELHO wrote:
> 
> About v11, ISTM that the recursive function should check for symbolic links
> and possibly avoid them:
> 
>  sh> cd data/base
>  sh> ln -s .. foo
> 
>  psql> SELECT * FROM pg_ls_dir_recurse('.');
>  ERROR:  could not stat file "./base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo": Too many levels of symbolic links
>  CONTEXT:  SQL function "pg_ls_dir_recurse" statement 1
> 
> This probably means using lstat instead of (in supplement to?) stat, and
> probably tell if something is a link, and if so not recurse in them.

Thanks for looking.

I think that opens up a can of worms.  I don't want to go into the business of
re-implementing all of find(1) - I count ~128 flags (most of which take
arguments).  You're referring to find -L vs find -P, and some people would want
one and some would want another.  And don't forget about find -H...

pg_stat_file doesn't expose the file type (I guess because it's not portable?),
and I think it's outside the scope of this patch to change that.  Maybe it
suggests that the pg_ls_dir_recurse patch should be excluded.

ISTM if someone wants to recursively list a directory, they should avoid
putting cycles there, or permission errors, or similar.  Or they should write
their own C extension that borrows from pg_ls_dir_files but handles more
arguments.

-- 
Justin





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 15:41                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-16 18:21                   ` Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 0 replies; 70+ messages in thread

From: Fabien COELHO @ 2020-03-16 18:21 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>


Hello Justin,

>>  psql> SELECT * FROM pg_ls_dir_recurse('.');
>>  ERROR:  could not stat file "./base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo/base/foo": Too many levels of symbolic links
>>  CONTEXT:  SQL function "pg_ls_dir_recurse" statement 1
>>
>> This probably means using lstat instead of (in supplement to?) stat, and
>> probably tell if something is a link, and if so not recurse in them.
>
> Thanks for looking.
>
> I think that opens up a can of worms.  I don't want to go into the business of
> re-implementing all of find(1) - I count ~128 flags (most of which take
> arguments).  You're referring to find -L vs find -P, and some people would want
> one and some would want another.  And don't forget about find -H...

This is not the point. The point is that a link can change a finite tree 
into cyclic graph, and you do not want to delve into that, ever.

The "find" command, by default, does not recurse into a link because of 
said problem, and the user *must* ask for it and assume the infinite loop 
if any.

So if you implement one behavior, it should be not recursing into links. 
Franckly, I would not provide the recurse into link alternative, but it 
could be implemented if someone wants it, and the problem that come with 
it.

> pg_stat_file doesn't expose the file type (I guess because it's not portable?),

You are right that Un*x and Windows are not the same wrt link. It seems 
that there is already something about that in port:

   "./src/port/dirmod.c:pgwin32_is_junction(const char *path)"

So most of the details are already hidden.

> and I think it's outside the scope of this patch to change that.  Maybe it
> suggests that the pg_ls_dir_recurse patch should be excluded.

IMHO, I really think that it should be included. Dealing with links is no 
big deal, but you need an additional column in _metadata to tell it is a 
link, and there is a ifdef because testing is a little different between 
unix and windows. I'd guess around 10-20 lines of code added.

> ISTM if someone wants to recursively list a directory, they should avoid
> putting cycles there, or permission errors, or similar.

Hmmm. I'd say the user should like to be able to call the function and 
never have a bad experience with it such as a failure on an infinite loop.

> Or they should write their own C extension that borrows from 
> pg_ls_dir_files but handles more arguments.

ISTM that the point of your patch is to provide the basic tool needed to 
list directories contents, and handling links somehow is a necessary part 
of that.

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-03-16 21:48                 ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-16 21:48 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Mon, Mar 16, 2020 at 04:20:21PM +0100, Fabien COELHO wrote:
> This probably means using lstat instead of (in supplement to?) stat, and
> probably tell if something is a link, and if so not recurse in them.

On Mon, Mar 16, 2020 at 07:21:06PM +0100, Fabien COELHO wrote:
> IMHO, I really think that it should be included. Dealing with links is no
> big deal, but you need an additional column in _metadata to tell it is a
> link

Instead of showing another column, I changed to show links with isdir=false.
At the cost of two more patches, to allow backpatching docs and maybe separate
commit to make the subtle change obvious in commit history, at least.

I see a few places in the backend and a few more in the fronted using the same
logic that I used for islink(), but I'm not sure if there's a good place to put
that to allow factoring out at least the other backend ones.

-- 
Justin

Attachments:

  [text/x-diff] v12-0001-Document-historic-behavior-about-hiding-director.patch (1.7K, ../../20200316214806.GQ26184@telsasoft.com/2-v12-0001-Document-historic-behavior-about-hiding-director.patch)
  download | inline diff:
From 1bb8e0efb4f14fa344cd5ee66c3138184a9fa9e2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 16:50:07 -0600
Subject: [PATCH v12 01/11] Document historic behavior about hiding directories
 and special files

Should backpatch to v10: tmpdir, waldir and archive_statusdir
---
 doc/src/sgml/func.sgml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..4c0ea5ab3f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21450,6 +21450,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     (mtime) of each file in the log directory. By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21461,6 +21462,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
@@ -21473,6 +21475,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v12-0002-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20200316214806.GQ26184@telsasoft.com/3-v12-0002-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From b6eb6cd9299e8ae07770790f05da038fede5278c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v12 02/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c0ea5ab3f..ace95fe661 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21527,7 +21527,8 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory.  Typical usages include:
+    indicating if it is a directory (or a symbolic link to a directory).
+    Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
 SELECT (pg_stat_file('filename')).modification;
-- 
2.17.0

  [text/x-diff] v12-0003-Document-historic-behavior-about-hiding-director.patch (906B, ../../20200316214806.GQ26184@telsasoft.com/4-v12-0003-Document-historic-behavior-about-hiding-director.patch)
  download | inline diff:
From 52f5b76cc278c43983b044b7fb0f04c2d848d61e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:12:04 -0600
Subject: [PATCH v12 03/11] Document historic behavior about hiding directories
 and special files

Should backpatch to v12: tmpdir
---
 doc/src/sgml/func.sgml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ace95fe661..2c6142a0e0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21489,6 +21489,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
+    Filenames beginning with a dot, directories, and other special files are not shown.
    </para>
 
    <indexterm>
-- 
2.17.0

  [text/x-diff] v12-0004-Add-tests-exercizing-pg_ls_tmpdir.patch (4.3K, ../../20200316214806.GQ26184@telsasoft.com/5-v12-0004-Add-tests-exercizing-pg_ls_tmpdir.patch)
  download | inline diff:
From dd98e2b4f1fb37065a317719f0dd493e8837c7e7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v12 04/11] Add tests exercizing pg_ls_tmpdir..

..and its backing function pg_ls_dir_files
---
 src/test/regress/expected/misc_functions.out | 7 +++++++
 src/test/regress/input/tablespace.source     | 5 +++++
 src/test/regress/output/tablespace.source    | 8 ++++++++
 src/test/regress/sql/misc_functions.sql      | 4 ++++
 4 files changed, 24 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e217b678d7..9947d9ef9d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -180,6 +180,13 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
  t
 (1 row)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 1e11eb3554..656aad93e6 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -51,6 +51,10 @@ from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1;
 
 select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v12-0005-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (11.6K, ../../20200316214806.GQ26184@telsasoft.com/6-v12-0005-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 8e983600c3f85b9e5f8ed15c8b5cba5dd4f5c0fb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 17:15:02 -0500
Subject: [PATCH v12 05/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml          |  17 +++
 src/backend/utils/adt/genfile.c | 194 ++++++++++++++++++--------------
 src/include/catalog/pg_proc.dat |   6 +
 3 files changed, 132 insertions(+), 85 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2c6142a0e0..4b966ed847 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         List the contents of a directory.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+        For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_metadata</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_metadata</function> lists the files in the specified
+    directory along with the file's metadata.
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bcf9bd1b97..98ab9a2b92 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,14 +36,23 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
-typedef struct
-{
-	char	   *location;
-	DIR		   *dirdesc;
-	bool		include_dot_dirs;
-} directory_fctx;
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -447,67 +456,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	FuncCallContext *funcctx;
-	struct dirent *de;
-	directory_fctx *fctx;
-	MemoryContext oldcontext;
-
-	if (SRF_IS_FIRSTCALL())
-	{
-		bool		missing_ok = false;
-		bool		include_dot_dirs = false;
-
-		/* check the optional arguments */
-		if (PG_NARGS() == 3)
-		{
-			if (!PG_ARGISNULL(1))
-				missing_ok = PG_GETARG_BOOL(1);
-			if (!PG_ARGISNULL(2))
-				include_dot_dirs = PG_GETARG_BOOL(2);
-		}
-
-		funcctx = SRF_FIRSTCALL_INIT();
-		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
-
-		fctx = palloc(sizeof(directory_fctx));
-		fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-		fctx->include_dot_dirs = include_dot_dirs;
-		fctx->dirdesc = AllocateDir(fctx->location);
-
-		if (!fctx->dirdesc)
-		{
-			if (missing_ok && errno == ENOENT)
-			{
-				MemoryContextSwitchTo(oldcontext);
-				SRF_RETURN_DONE(funcctx);
-			}
-			else
-				ereport(ERROR,
-						(errcode_for_file_access(),
-						 errmsg("could not open directory \"%s\": %m",
-								fctx->location)));
-		}
-		funcctx->user_fctx = fctx;
-		MemoryContextSwitchTo(oldcontext);
-	}
-
-	funcctx = SRF_PERCALL_SETUP();
-	fctx = (directory_fctx *) funcctx->user_fctx;
-
-	while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
-	{
-		if (!fctx->include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
-	}
-
-	FreeDir(fctx->dirdesc);
-
-	SRF_RETURN_DONE(funcctx);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -520,7 +471,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -530,7 +483,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -539,6 +492,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -554,8 +533,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	} else {
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -574,7 +563,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
 		{
 			tuplestore_donestoring(tupstore);
 			return (Datum) 0;
@@ -584,13 +573,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -600,13 +596,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +631,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -646,7 +656,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
 }
 
 /*
@@ -675,5 +686,18 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
-- 
2.17.0

  [text/x-diff] v12-0006-Show-links-to-dirs-with-isdir-false.patch (2.6K, ../../20200316214806.GQ26184@telsasoft.com/7-v12-0006-Show-links-to-dirs-with-isdir-false.patch)
  download | inline diff:
From 38704c29b593e32404ad13f93d0bce6330ee41d1 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 17:15:02 -0500
Subject: [PATCH v12 06/11] Show links to dirs with isdir=false..

..this is needed to avoid infinite recursion in pg_ls_dir_recurse.

Change pg_stat_file for consistency.
---
 doc/src/sgml/func.sgml          |  2 +-
 src/backend/utils/adt/genfile.c | 21 +++++++++++++++++++--
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4b966ed847..68c7327e1d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21545,7 +21545,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory (or a symbolic link to a directory).
+    indicating if it is a directory (and not a symbolic link to a directory).
     Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 98ab9a2b92..70b541388e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -365,6 +365,22 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return true iff path is a symbolic link (unix) or junction (win32) */
+static bool
+islink(const char *path)
+{
+#ifndef WIN32
+	struct stat attrib;
+	if (lstat(path, &attrib) < 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not stat file \"%s\": %m", path)));
+	return S_ISLNK(attrib.st_mode);
+#else
+	return pgwin32_is_junction(path);
+#endif
+}
+
 /*
  * stat a file
  */
@@ -428,7 +444,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode) && !islink(filename));
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -614,7 +630,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			values[1] = Int64GetDatum((int64) attrib.st_size);
 			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
 			if (flags & LS_DIR_ISDIR)
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode) &&
+					!islink(path));
 		}
 
 		memset(nulls, 0, sizeof(nulls));
-- 
2.17.0

  [text/x-diff] v12-0007-pg_ls_tmpdir-to-show-isdir-argument.patch (7.6K, ../../20200316214806.GQ26184@telsasoft.com/8-v12-0007-pg_ls_tmpdir-to-show-isdir-argument.patch)
  download | inline diff:
From c0d3aa1dd505d3da12d4023b59c4d717c3282a9e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v12 07/11] pg_ls_tmpdir to show "isdir" argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Note that "dir_ok" means both: do not skip dirs, *and* show "isdir" column.
The other functions both skip dirs and only show 3 columns, and ls_tmpdir is
changing to both show dirs and show 4 columns.  It wouldn't make sense to skip
dirs and then show a 4th column always false.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 22 +++++++++++---------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 +++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 68c7327e1d..b813b19daf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21391,12 +21391,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21499,14 +21499,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 70b541388e..0cfa055690 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -493,7 +493,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -674,7 +674,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR|LS_DIR_METADATA|LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0a1859f709..7789d029ea 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10733,13 +10733,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 9947d9ef9d..b0cf145dc5 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -183,8 +183,8 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v12-0008-pg_ls_-dir-to-show-directories-and-isdir-column.patch (10.6K, ../../20200316214806.GQ26184@telsasoft.com/9-v12-0008-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 37084a28fccb68d5f820a7a52eccebfa054ed6d1 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v12 08/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 39 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 ++++--
 src/include/catalog/pg_proc.dat              | 19 +++++-----
 src/test/regress/expected/misc_functions.out |  4 +-
 4 files changed, 41 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b813b19daf..4cdd03610a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21357,8 +21357,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21368,8 +21369,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21379,8 +21381,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21463,36 +21466,38 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0cfa055690..de63e77836 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -53,6 +53,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_MODERN				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -648,14 +651,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_MODERN);
 }
 
 /*
@@ -674,7 +677,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR|LS_DIR_METADATA|LS_DIR_MISSING_OK);
+			LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /*
@@ -704,7 +707,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+			LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7789d029ea..b4c3d15495 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6143,6 +6143,7 @@
   proname => 'pg_ls_dir', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'text', proargtypes => 'text bool bool',
   prosrc => 'pg_ls_dir' },
+
 { oid => '2626', descr => 'sleep for the specified time in seconds',
   proname => 'pg_sleep', provolatile => 'v', prorettype => 'void',
   proargtypes => 'float8', prosrc => 'pg_sleep' },
@@ -10717,18 +10718,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -10743,9 +10744,9 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
-  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b0cf145dc5..448901d841 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v12-0009-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (5.3K, ../../20200316214806.GQ26184@telsasoft.com/10-v12-0009-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 3ef2b34083114e760bb63843bd92fb83f8ac49e5 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v12 09/11] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

TODO:
src/backend/catalog/system_views.sql:REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 24 ++++++++++++++++++++
 src/include/catalog/pg_proc.dat              |  6 +++++
 src/test/regress/expected/misc_functions.out | 14 ++++++++++++
 src/test/regress/sql/misc_functions.sql      |  6 +++++
 4 files changed, 50 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4cdd03610a..373e3b647b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21351,6 +21351,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_recurse(<parameter>dirname</parameter> <type>text</type>)</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+         Call pg_ls_dir_metadata to recursively list the files in the specified directory, along with each file's metadata.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21462,6 +21471,21 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     directory along with the file's metadata.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+format('/PG_%s_%s', left(current_setting('server_version_num'), 2), catalog_version_no) AS suffix) AS dir,
+pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b4c3d15495..667ea5721e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10748,6 +10748,12 @@
   proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
   proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8511', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 448901d841..82d967a62d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -187,6 +187,20 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 656aad93e6..2cfdac386d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -55,6 +55,12 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v12-0010-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.0K, ../../20200316214806.GQ26184@telsasoft.com/11-v12-0010-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 3b03b4e911db0c9b9386667ad0c805dc97ae6607 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v12 10/11] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index de63e77836..810c6b0f23 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -651,7 +651,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v12-0011-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (15.8K, ../../20200316214806.GQ26184@telsasoft.com/12-v12-0011-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 5059e4fb8528657b0b9e81a24c70fe9e56aeb677 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v12 11/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 30 +++++++++-----------
 src/backend/utils/adt/genfile.c              | 24 ++++++++++------
 src/include/catalog/pg_proc.dat              | 30 ++++++++++----------
 src/test/regress/expected/misc_functions.out | 12 ++++----
 src/test/regress/output/tablespace.source    |  8 +++---
 5 files changed, 54 insertions(+), 50 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 373e3b647b..11309f0f9b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21366,8 +21366,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21378,8 +21377,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21390,9 +21388,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21404,8 +21401,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -21491,8 +21487,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -21503,8 +21499,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21516,8 +21512,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21530,8 +21526,8 @@ pg_ls_dir_recurse(dir) AS a;
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 810c6b0f23..3a435384f4 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -592,8 +592,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -627,17 +627,25 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
 		{
 			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode) &&
-					!islink(path));
-		}
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_atime));
+			values[3] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+			values[4] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+			nulls[5] = true;
+#else
+			nulls[4] = true;
+			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+#endif
 
-		memset(nulls, 0, sizeof(nulls));
+			values[6] = BoolGetDatum(S_ISDIR(attrib.st_mode) &&
+				!islink(path));
+		}
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 667ea5721e..b74ed64b71 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10718,42 +10718,42 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8511', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification,a.change,a.creation,a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 82d967a62d..3544977a51 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -183,8 +183,8 @@ select count(*) >= 0 as ok from pg_ls_archive_statusdir();
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -197,8 +197,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-16 22:17                   ` Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Alvaro Herrera @ 2020-03-16 22:17 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

I pushed 0001 and 0003 (as a single commit).  archive_statusdir didn't
get here until 12, so your commit message was mistaken.  Also, pg10 is
slightly different so it didn't apply there, so I left it alone.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
@ 2020-03-17 03:14                     ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-17 03:14 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Mon, Mar 16, 2020 at 07:17:36PM -0300, Alvaro Herrera wrote:
> I pushed 0001 and 0003 (as a single commit).  archive_statusdir didn't
> get here until 12, so your commit message was mistaken.  Also, pg10 is
> slightly different so it didn't apply there, so I left it alone.

Thanks, I appreciate it (and I'm sure Fabien will appreciate having two fewer
patches...).

@cfbot: rebased onto b4570d33aa045df330bb325ba8a2cbf02266a555

I realized that if I lstat() a file to make sure links to dirs show as
isdir=false, it's odd to then show size and timestamps of the dir.  So changed
to use lstat ... and squished.

-- 
Justin

Attachments:

  [text/x-diff] v13-0001-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20200317031427.GU26184@telsasoft.com/2-v13-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From d8294c4747c5ba1f3bec858c137cc2d31e5a0425 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v13 1/8] Document historic behavior of links to directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..2c6142a0e0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21528,7 +21528,8 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory.  Typical usages include:
+    indicating if it is a directory (or a symbolic link to a directory).
+    Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
 SELECT (pg_stat_file('filename')).modification;
-- 
2.17.0

  [text/x-diff] v13-0002-Add-tests-exercising-pg_ls_tmpdir.patch (4.3K, ../../20200317031427.GU26184@telsasoft.com/3-v13-0002-Add-tests-exercising-pg_ls_tmpdir.patch)
  download | inline diff:
From 27913ef889696ef2c42ed52bf7097e4a5aeaad9e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v13 2/8] Add tests exercising pg_ls_tmpdir..

..and its backing function pg_ls_dir_files
---
 src/test/regress/expected/misc_functions.out | 7 +++++++
 src/test/regress/input/tablespace.source     | 5 +++++
 src/test/regress/output/tablespace.source    | 8 ++++++++
 src/test/regress/sql/misc_functions.sql      | 4 ++++
 4 files changed, 24 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..903f1fe443 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,13 @@ select count(*) > 0 from
  t
 (1 row)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f09c890b5d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,10 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v13-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (14.3K, ../../20200317031427.GU26184@telsasoft.com/4-v13-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From afc6064e9a93fb0f00eefefe706c59d6325d2727 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 17:15:02 -0500
Subject: [PATCH v13 3/8] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Change to use lstat() to allow pg_ls_dir_recurse to avoid infinite recursion.
That means:
 - links to dirs are shown with isdir=false;
 - timestamps shown are those of the link;
 - changed pg_stat_file for consistency;

Need catversion bumped?
---
 doc/src/sgml/func.sgml               |  19 ++-
 src/backend/catalog/system_views.sql |   1 +
 src/backend/utils/adt/genfile.c      | 218 +++++++++++++++------------
 src/include/catalog/pg_proc.dat      |   6 +
 4 files changed, 150 insertions(+), 94 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2c6142a0e0..68c7327e1d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         List the contents of a directory.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+        For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_metadata</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_metadata</function> lists the files in the specified
+    directory along with the file's metadata.
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
@@ -21528,7 +21545,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory (or a symbolic link to a directory).
+    indicating if it is a directory (and not a symbolic link to a directory).
     Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46912..05a644a7c9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1435,6 +1435,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 01185f218b..4b70a00a35 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -370,7 +385,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -413,6 +428,10 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	if (pgwin32_is_junction(path)) /* Links are not directories */
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -440,79 +459,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -525,7 +474,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -535,7 +486,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -544,6 +495,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +535,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	} else {
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,36 +565,67 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
+		{
+			tuplestore_donestoring(tupstore);
 			return (Datum) 0;
+		}
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links are not directories */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +639,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -646,7 +664,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
 }
 
 /*
@@ -675,5 +694,18 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
-- 
2.17.0

  [text/x-diff] v13-0004-pg_ls_tmpdir-to-show-isdir-argument.patch (7.3K, ../../20200317031427.GU26184@telsasoft.com/5-v13-0004-pg_ls_tmpdir-to-show-isdir-argument.patch)
  download | inline diff:
From 3cee15e9332b3019120fc27d31d0e147d381ea5d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v13 4/8] pg_ls_tmpdir to show "isdir" argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 22 +++++++++++---------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 +++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 68c7327e1d..b813b19daf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21391,12 +21391,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21499,14 +21499,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4b70a00a35..b35467e522 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -480,7 +480,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -665,7 +665,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR|LS_DIR_METADATA|LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0a1859f709..7789d029ea 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10733,13 +10733,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 903f1fe443..dd78d9b20f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -204,8 +204,8 @@ select count(*) > 0 from
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v13-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (10.2K, ../../20200317031427.GU26184@telsasoft.com/6-v13-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 82cc15b0ae49d82fb51eb27aff8fb221a6335f5b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v13 5/8] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 39 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 ++++--
 src/include/catalog/pg_proc.dat              | 18 ++++-----
 src/test/regress/expected/misc_functions.out |  4 +-
 4 files changed, 40 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b813b19daf..4cdd03610a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21357,8 +21357,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21368,8 +21369,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21379,8 +21381,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21463,36 +21466,38 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index b35467e522..342d6f1205 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_MODERN				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -639,14 +642,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_MODERN);
 }
 
 /*
@@ -665,7 +668,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR|LS_DIR_METADATA|LS_DIR_MISSING_OK);
+			LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /*
@@ -695,7 +698,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+			LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7789d029ea..be3e820a03 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10717,18 +10717,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -10743,9 +10743,9 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
-  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index dd78d9b20f..3ca4b85dd9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v13-0006-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (6.1K, ../../20200317031427.GU26184@telsasoft.com/7-v13-0006-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 0ed2eb8ad917bf5a1130aaf9050b0c3d73f66fba Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v13 6/8] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

XXX: this test case is unstable, if backends/autovacuum remove FSM...
ERROR:  could not stat file "./base/16384/20345_fsm": No such file or directory
CONTEXT:  SQL function "pg_ls_dir_recurse" statement 1

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 25 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 +++++
 src/test/regress/expected/misc_functions.out | 14 +++++++++++
 src/test/regress/sql/misc_functions.sql      |  6 +++++
 5 files changed, 52 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4cdd03610a..0763db3f50 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21351,6 +21351,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_recurse(<parameter>dirname</parameter> <type>text</type>)</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+         Call pg_ls_dir_metadata to recursively list the files in the specified directory, along with each file's metadata.
+         Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21462,6 +21472,21 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     directory along with the file's metadata.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+format('/PG_%s_%s', left(current_setting('server_version_num'), 2), catalog_version_no) AS suffix) AS dir,
+pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 05a644a7c9..e7295d8aaf 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1436,6 +1436,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be3e820a03..b3509f0e12 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10747,6 +10747,12 @@
   proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
   proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8511', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 3ca4b85dd9..ae8716a83f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -208,6 +208,20 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f09c890b5d..7038bcdb0f 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -64,6 +64,12 @@ select count(*) > 0 from
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v13-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.0K, ../../20200317031427.GU26184@telsasoft.com/8-v13-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 43137b3dacfadd4f88f16a14972cf7a02c1917cf Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v13 7/8] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 342d6f1205..21c265aab3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -642,7 +642,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v13-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (16.0K, ../../20200317031427.GU26184@telsasoft.com/9-v13-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From f8cfd3da5a2e8f960bca9a550fdd5acf705c59b7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v13 8/8] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 30 +++++++++-----------
 src/backend/utils/adt/genfile.c              | 29 +++++++++++--------
 src/include/catalog/pg_proc.dat              | 30 ++++++++++----------
 src/test/regress/expected/misc_functions.out | 12 ++++----
 src/test/regress/output/tablespace.source    |  8 +++---
 5 files changed, 55 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0763db3f50..a3166aaf54 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21367,8 +21367,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21379,8 +21378,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21391,9 +21389,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21405,8 +21402,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -21492,8 +21488,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -21504,8 +21500,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21517,8 +21513,8 @@ pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21531,8 +21527,8 @@ pg_ls_dir_recurse(dir) AS a;
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 21c265aab3..f794352476 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -578,8 +578,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -613,24 +613,29 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
 		{
 			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_atime));
+			values[3] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+			values[4] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+			nulls[5] = true;
+#else
+			nulls[4] = true;
+			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+#endif
+			values[6] = BoolGetDatum(S_ISDIR(attrib.st_mode));
 #ifdef WIN32
-				/* Links are not directories */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
+			/* Links are not directories */
+			if (pgwin32_is_junction(path))
+				values[6] = BoolGetDatum(false);
 #endif
-			}
 		}
 
-		memset(nulls, 0, sizeof(nulls));
-
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b3509f0e12..a50742e870 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10717,42 +10717,42 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8511', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification,a.change,a.creation,a.isdir from ls, pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ae8716a83f..5614e564bd 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -204,8 +204,8 @@ select count(*) > 0 from
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -218,8 +218,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-17 09:21                       ` Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Fabien COELHO @ 2020-03-17 09:21 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>


About v13, seens as one patch:

Function "pg_ls_dir_metadata" documentation suggests a variable number of 
arguments with brackets, but parameters are really mandatory.

  postgres=# SELECT pg_ls_dir_metadata('.');
  ERROR:  function pg_ls_dir_metadata(unknown) does not exist
  LINE 1: SELECT pg_ls_dir_metadata('.');
                ^
  HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
  postgres=# SELECT pg_ls_dir_metadata('.', true, true);
  …

The example in the documentation could be better indented. Also, ISTM that 
there are two implicit laterals (format & pg_ls_dir_recurse) that I would 
make explicit. I'd use the pcs alias explicitely. I'd use meaningful 
aliases (eg ts instead of b, …).

On reflection, I think that a boolean "isdir" column is a bad idea because 
it is not extensible. I'd propose to switch to the standard "ls" approach 
of providing the type as one character: '-' for regular, 'd' for 
directory, 'l' for link, 's' for socket, 'c' for character special…

ISTM that "lstat" is not available on windows, which suggests to call 
"stat" always, and then "lstat" on un*x and pg ports stuff on win.

I'm wondering about the restriction on directories only. Why should it not 
work on a file? Can it be easily extended to work on a simple file? If so, 
it could be just "pg_ls".

-- 
Fabien.

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-03-17 19:04                         ` Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 19:11                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-03-17 19:04 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Tue, Mar 17, 2020 at 10:21:48AM +0100, Fabien COELHO wrote:
> 
> About v13, seens as one patch:
> 
> Function "pg_ls_dir_metadata" documentation suggests a variable number of
> arguments with brackets, but parameters are really mandatory.

Fixed, and added tests on 1 and 3 arg versions of both pg_ls_dir() and
pg_ls_dir_metadata().

It seems like the only way to make variable number of arguments is is with
multiple entries in pg_proc.dat, one for each "number of" arguments.  Is that
right ?

> The example in the documentation could be better indented. Also, ISTM that
> there are two implicit laterals (format & pg_ls_dir_recurse) that I would
> make explicit. I'd use the pcs alias explicitely. I'd use meaningful aliases
> (eg ts instead of b, …).

> On reflection, I think that a boolean "isdir" column is a bad idea because
> it is not extensible. I'd propose to switch to the standard "ls" approach of
> providing the type as one character: '-' for regular, 'd' for directory, 'l'
> for link, 's' for socket, 'c' for character special…

I think that's outside the scope of the patch, since I'd want to change
pg_stat_file; that's where I borrowed "isdir" from, for consistency.

Note that both LS_DIR_HISTORIC and LS_DIR_MODERN include LS_DIR_SKIP_SPECIAL,
so only pg_ls_dir itself show specials, so they way to do it would be to 1)
change pg_stat_file to expose the file's "type", 2) use pg_ls_dir() AS a,
lateral pg_stat_file(a) AS b, 3) then consider also changing LS_DIR_MODERN and
all the existing pg_ls_*.

> ISTM that "lstat" is not available on windows, which suggests to call "stat"
> always, and then "lstat" on un*x and pg ports stuff on win.

I believe that's handled here.
src/include/port/win32_port.h:#define lstat(path, sb) stat(path, sb)

> I'm wondering about the restriction on directories only. Why should it not
> work on a file? Can it be easily extended to work on a simple file? If so,
> it could be just "pg_ls".

I think that's a good idea, except it doesn't fit with what the code does:
AllocDir() and ReadDir().  Instead, use pg_stat_file() for that.

Hm, I realized that the existing pg_ls_dir_metadata was skipping links to dirs,
since !ISREG().  So changed to use both stat() and lstat().

-- 
Justin

Attachments:

  [text/x-diff] v14-0001-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20200317190401.GY26184@telsasoft.com/2-v14-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From d8294c4747c5ba1f3bec858c137cc2d31e5a0425 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v14 1/8] Document historic behavior of links to directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..2c6142a0e0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21528,7 +21528,8 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory.  Typical usages include:
+    indicating if it is a directory (or a symbolic link to a directory).
+    Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
 SELECT (pg_stat_file('filename')).modification;
-- 
2.17.0

  [text/x-diff] v14-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200317190401.GY26184@telsasoft.com/3-v14-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 031ce2684587d6d8d84c6d4b6c87f5399b6efb78 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v14 2/8] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v14-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (20.4K, ../../20200317190401.GY26184@telsasoft.com/4-v14-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 8ad4e2f920659df6656b6074b851e5718d8e948b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v14 3/8] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Change to use lstat() to allow pg_ls_dir_recurse to avoid infinite recursion.
That means:
 - links to dirs are shown with isdir=false;
 - timestamps shown are those of the link;
 - changed pg_stat_file for consistency;

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  19 +-
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 244 ++++++++++++-------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 230 insertions(+), 94 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2c6142a0e0..68c7327e1d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         List the contents of a directory.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+        For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_metadata</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_metadata</function> lists the files in the specified
+    directory along with the file's metadata.
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
@@ -21528,7 +21545,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory (or a symbolic link to a directory).
+    indicating if it is a directory (and not a symbolic link to a directory).
     Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46912..05a644a7c9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1435,6 +1435,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 01185f218b..daae06aa9a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -370,7 +385,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	} else {
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,36 +566,77 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
+		{
+			tuplestore_donestoring(tupstore);
 			return (Datum) 0;
+		}
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
+		struct stat lattrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (stat(path, &attrib) < 0 ||
+			lstat(path, &lattrib) < 0)
 			ereport(ERROR,
 					(errcode_for_file_access(),
 					 errmsg("could not stat file \"%s\": %m", path)));
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(lattrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (S_ISDIR(attrib.st_mode))
+		{
+			/*
+			 * Do nothing: links to dirs are not skipped (but are shown as
+			 * isdir=false)
+			 */
+			;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) lattrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(lattrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(lattrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +650,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -646,7 +675,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -675,5 +705,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..b1a957fe3e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir 
+------+-------
+ .    | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir 
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v14-0004-pg_ls_tmpdir-to-show-isdir-argument.patch (7.4K, ../../20200317190401.GY26184@telsasoft.com/5-v14-0004-pg_ls_tmpdir-to-show-isdir-argument.patch)
  download | inline diff:
From 12c87d95527ec530c52da5272c41038026555897 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v14 4/8] pg_ls_tmpdir to show "isdir" argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 22 +++++++++++---------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 +++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 68c7327e1d..b813b19daf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21391,12 +21391,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21499,14 +21499,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index daae06aa9a..5b10c1df9b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -676,7 +676,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b1a957fe3e..fae5b5194a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10733,13 +10733,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v14-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.4K, ../../20200317190401.GY26184@telsasoft.com/6-v14-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From b90d77abe7080233b5cd35680b5d25c7d4418b07 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v14 5/8] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 39 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 ++++--
 src/include/catalog/pg_proc.dat              | 12 +++---
 src/test/regress/expected/misc_functions.out |  4 +-
 4 files changed, 37 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b813b19daf..4cdd03610a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21357,8 +21357,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21368,8 +21369,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21379,8 +21381,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21463,36 +21466,38 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5b10c1df9b..21611dfbc0 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -650,14 +653,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -676,7 +679,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -706,7 +709,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fae5b5194a..2c5829bca4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10717,18 +10717,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 38493de732..64b1417fb8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v14-0006-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (6.1K, ../../20200317190401.GY26184@telsasoft.com/7-v14-0006-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 0db676c0c08cedf927b80b330463f21b48a6983b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v14 6/8] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

XXX: this test case is unstable, if backends/autovacuum remove FSM...
ERROR:  could not stat file "./base/16384/20345_fsm": No such file or directory
CONTEXT:  SQL function "pg_ls_dir_recurse" statement 1

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 25 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 +++++
 src/test/regress/expected/misc_functions.out | 14 +++++++++++
 src/test/regress/sql/misc_functions.sql      |  6 +++++
 5 files changed, 52 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4cdd03610a..b30ac9f10d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21351,6 +21351,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_recurse(<parameter>dirname</parameter> <type>text</type>)</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+         Call pg_ls_dir_metadata to recursively list the files in the specified directory, along with each file's metadata.
+         Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21462,6 +21472,21 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     directory along with the file's metadata.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+lateral format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+lateral pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 05a644a7c9..e7295d8aaf 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1436,6 +1436,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2c5829bca4..af2c2dd621 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10753,6 +10753,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '5034', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 64b1417fb8..4188d684f0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,20 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 372345720d..6041c4f3dc 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v14-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.0K, ../../20200317190401.GY26184@telsasoft.com/8-v14-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 5ce104119543729151f1a80b40e9565ad3afa521 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v14 7/8] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 21611dfbc0..00de0c091d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -653,7 +653,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v14-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.2K, ../../20200317190401.GY26184@telsasoft.com/9-v14-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 362e434f64a478a4f74339f4291e0d48eccd1ec6 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v14 8/8] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 30 ++++++++---------
 src/backend/utils/adt/genfile.c              | 29 ++++++++++-------
 src/include/catalog/pg_proc.dat              | 34 ++++++++++----------
 src/test/regress/expected/misc_functions.out | 16 ++++-----
 src/test/regress/output/tablespace.source    |  8 ++---
 5 files changed, 59 insertions(+), 58 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b30ac9f10d..a815fe7b7d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21367,8 +21367,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21379,8 +21378,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21391,9 +21389,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21405,8 +21402,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -21492,8 +21488,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -21504,8 +21500,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21517,8 +21513,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21531,8 +21527,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 00de0c091d..5750c5d949 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -579,8 +579,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 		struct stat lattrib;
@@ -624,24 +624,29 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
 		{
 			values[1] = Int64GetDatum((int64) lattrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(lattrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(lattrib.st_mode));
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_atime));
+			values[3] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+			values[4] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+			nulls[5] = true;
+#else
+			nulls[4] = true;
+			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+#endif
+			values[6] = BoolGetDatum(S_ISDIR(lattrib.st_mode));
 #ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
+			/* Links should have isdir=false */
+			if (pgwin32_is_junction(path))
+				values[6] = BoolGetDatum(false);
 #endif
-			}
 		}
 
-		memset(nulls, 0, sizeof(nulls));
-
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index af2c2dd621..954e2cf7dd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10717,48 +10717,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '5033', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,name,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '5034', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification,a.change,a.creation,a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4188d684f0..cce84a60a9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
@@ -239,8 +239,8 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -253,8 +253,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-17 19:11                           ` Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 70+ messages in thread

From: Tom Lane @ 2020-03-17 19:11 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Justin Pryzby <pryzby@telsasoft.com> writes:
> It seems like the only way to make variable number of arguments is is with
> multiple entries in pg_proc.dat, one for each "number of" arguments.  Is that
> right ?

Another way to do it is to have one entry, put the full set of arguments
into the initial pg_proc.dat data, and then use CREATE OR REPLACE FUNCTION
later during initdb to install some defaults.  See existing cases in
system_views.sql, starting about line 1180.  Neither way is especially
pretty, so take your choice.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-03-31 20:08                           ` Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-03-31 20:08 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Tue, Mar 17, 2020 at 02:04:01PM -0500, Justin Pryzby wrote:
> > The example in the documentation could be better indented. Also, ISTM that
> > there are two implicit laterals (format & pg_ls_dir_recurse) that I would
> > make explicit. I'd use the pcs alias explicitely. I'd use meaningful aliases
> > (eg ts instead of b, …).
> 
> > On reflection, I think that a boolean "isdir" column is a bad idea because
> > it is not extensible. I'd propose to switch to the standard "ls" approach of
> > providing the type as one character: '-' for regular, 'd' for directory, 'l'
> > for link, 's' for socket, 'c' for character special…
> 
> I think that's outside the scope of the patch, since I'd want to change
> pg_stat_file; that's where I borrowed "isdir" from, for consistency.
> 
> Note that both LS_DIR_HISTORIC and LS_DIR_MODERN include LS_DIR_SKIP_SPECIAL,
> so only pg_ls_dir itself show specials, so they way to do it would be to 1)
> change pg_stat_file to expose the file's "type", 2) use pg_ls_dir() AS a,
> lateral pg_stat_file(a) AS b, 3) then consider also changing LS_DIR_MODERN and
> all the existing pg_ls_*.

The patch intends to fix the issue of "failing to show failed filesets"
(because dirs are skipped) while also generalizing existing functions (to show
directories and "isdir" column) and providing some more flexible ones (to list
file and metadata of a dir, which is currently possible [only] for "special"
directories, or by recursively calling pg_stat_file).

I'm still of the opinion that supporting arbitrary file types is out of scope,
but I changed the "isdir" to show "type".  I'm only supporting '[-dl]'.  I
don't want to have to check #ifdef S_ISDOOR or whatever other vendors have.  I
insist that it is a separate patch, since it depends on everything else, and I
have no feedback from anybody else as to whether any of that is desired.

template1=# SELECT * FROM pg_ls_waldir();
           name           |   size   |         access         |      modification      |         change         | creation | type 
--------------------------+----------+------------------------+------------------------+------------------------+----------+------
 barr                     |        0 | 2020-03-31 14:43:11-05 | 2020-03-31 14:43:11-05 | 2020-03-31 14:43:11-05 |          | ?
 baz                      |     4096 | 2020-03-31 14:39:18-05 | 2020-03-31 14:39:18-05 | 2020-03-31 14:39:18-05 |          | d
 foo                      |        0 | 2020-03-31 14:39:37-05 | 2020-03-31 14:39:37-05 | 2020-03-31 14:39:37-05 |          | -
 archive_status           |     4096 | 2020-03-31 14:38:20-05 | 2020-03-31 14:38:18-05 | 2020-03-31 14:38:18-05 |          | d
 000000010000000000000001 | 16777216 | 2020-03-31 14:42:53-05 | 2020-03-31 14:43:08-05 | 2020-03-31 14:43:08-05 |          | -
 bar                      |        3 | 2020-03-31 14:39:16-05 | 2020-03-31 14:39:01-05 | 2020-03-31 14:39:01-05 |          | l

Attachments:

  [text/x-diff] v15-0001-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20200331200812.GI14618@telsasoft.com/2-v15-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From f6b54482cc1d1b9595f49211b14807a20f994a3f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v15 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7a0bb0c70a..25b1278459 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21558,7 +21558,8 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory.  Typical usages include:
+    indicating if it is a directory (or a symbolic link to a directory).
+    Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
 SELECT (pg_stat_file('filename')).modification;
-- 
2.17.0

  [text/x-diff] v15-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.2K, ../../20200331200812.GI14618@telsasoft.com/3-v15-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From e1c8750fb777df4bf41f36df7ae6cde39b907ec2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v15 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 25b1278459..630f03d7dd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21558,7 +21558,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory (or a symbolic link to a directory).
+    indicating if it is a directory.
     Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ceaa6180da..219ac160f8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -370,7 +370,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -596,7 +596,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v15-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200331200812.GI14618@telsasoft.com/4-v15-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 4c6e234cfee829f0e14f1d4cfb6c0a7c77c86c9b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v15 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v15-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.4K, ../../20200331200812.GI14618@telsasoft.com/5-v15-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From e255804b9e50e869bb2ba7538349ff2b9cb26133 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v15 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

XXX: Change to use lstat() to allow pg_ls_dir_recurse to avoid infinite recursion.
That means:
 - links to dirs are NOT SHOWN (was: shown with isdir=false;)
 - NO: timestamps shown are those of the link;
 - changed pg_stat_file for consistency;

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  17 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 227 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 214 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 630f03d7dd..ed8e1977ba 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21372,6 +21372,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         List the contents of a directory.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+        For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21472,6 +21481,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_metadata</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_metadata</function> lists the files in the specified
+    directory along with the file's metadata.
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 83d00c6cde..4511f9d658 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1436,6 +1436,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..eaa7c548e0 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	} else {
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +566,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -606,13 +601,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +642,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -651,7 +667,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -680,5 +697,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a6a708cca9..e8846e1522 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10801,6 +10801,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir 
+------+-------
+ .    | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir 
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v15-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (7.4K, ../../20200331200812.GI14618@telsasoft.com/6-v15-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 3294321ae8a5a751b4e81e544e070a9106020b82 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v15 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 22 +++++++++++---------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 +++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ed8e1977ba..343af5e954 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21421,12 +21421,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -21529,14 +21529,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index eaa7c548e0..78edc85719 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -668,7 +668,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e8846e1522..40d951f029 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10793,13 +10793,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v15-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.4K, ../../20200331200812.GI14618@telsasoft.com/7-v15-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 374b0334ab7438c0155242fec26b81ae1ab3fb6b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v15 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 39 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 ++++--
 src/include/catalog/pg_proc.dat              | 12 +++---
 src/test/regress/expected/misc_functions.out |  4 +-
 4 files changed, 37 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 343af5e954..6a4623d59b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21387,8 +21387,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21398,8 +21399,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -21409,8 +21411,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21493,36 +21496,38 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 78edc85719..afef2bf4ab 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -642,14 +645,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -668,7 +671,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -698,7 +701,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 40d951f029..408bd40256 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10777,18 +10777,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 38493de732..64b1417fb8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v15-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (6.1K, ../../20200331200812.GI14618@telsasoft.com/8-v15-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 9a9f031b65a85b3b4085db3c293321c7f1a8b56f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v15 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

XXX: this test case is unstable, if backends/autovacuum remove FSM...
ERROR:  could not stat file "./base/16384/20345_fsm": No such file or directory
CONTEXT:  SQL function "pg_ls_dir_recurse" statement 1

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 25 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 +++++
 src/test/regress/expected/misc_functions.out | 14 +++++++++++
 src/test/regress/sql/misc_functions.sql      |  6 +++++
 5 files changed, 52 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6a4623d59b..7c8b102610 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21381,6 +21381,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         For each file in a directory, list the file and its metadata.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_recurse(<parameter>dirname</parameter> <type>text</type>)</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+         Call pg_ls_dir_metadata to recursively list the files in the specified directory, along with each file's metadata.
+         Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -21492,6 +21502,21 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     directory along with the file's metadata.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+lateral format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+lateral pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4511f9d658..e37ad20013 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1437,6 +1437,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 408bd40256..6c3b6f1f5b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10813,6 +10813,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '5034', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 64b1417fb8..4188d684f0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,20 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 372345720d..6041c4f3dc 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v15-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20200331200812.GI14618@telsasoft.com/9-v15-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From bf2e00ec7c3e1fda45d0d5d8cb34afd617cdc424 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v15 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index afef2bf4ab..e8dc1d1be5 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -645,7 +645,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v15-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.2K, ../../20200331200812.GI14618@telsasoft.com/10-v15-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 0192275d6918d7a13789fd77a56b55927839881e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v15 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 30 ++++++++---------
 src/backend/utils/adt/genfile.c              | 29 ++++++++++-------
 src/include/catalog/pg_proc.dat              | 34 ++++++++++----------
 src/test/regress/expected/misc_functions.out | 16 ++++-----
 src/test/regress/output/tablespace.source    |  8 ++---
 5 files changed, 59 insertions(+), 58 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c8b102610..88397ccddb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21397,8 +21397,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21409,8 +21408,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -21421,9 +21419,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -21435,8 +21432,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -21522,8 +21518,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -21534,8 +21530,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21547,8 +21543,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -21561,8 +21557,8 @@ lateral pg_ls_dir_recurse(dir) AS a;
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e8dc1d1be5..4d90f37a21 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -576,8 +576,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -616,24 +616,29 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
 		{
 			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_atime));
+			values[3] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+			values[4] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+			nulls[5] = true;
+#else
+			nulls[4] = true;
+			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
+#endif
+			values[6] = BoolGetDatum(S_ISDIR(attrib.st_mode));
 #ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
+			/* Links should have isdir=false */
+			if (pgwin32_is_junction(path))
+				values[6] = BoolGetDatum(false);
 #endif
-			}
 		}
 
-		memset(nulls, 0, sizeof(nulls));
-
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6c3b6f1f5b..d97ff8097f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10777,48 +10777,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '5033', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,name,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '5034', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4188d684f0..cce84a60a9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
@@ -239,8 +239,8 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -253,8 +253,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v15-0010-pg_ls_-to-show-file-type-and-show-special-files.patch (17.6K, ../../20200331200812.GI14618@telsasoft.com/11-v15-0010-pg_ls_-to-show-file-type-and-show-special-files.patch)
  download | inline diff:
From 9091a9138a8c2622e84ea2a06e922b8cb703be62 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v15 10/10] pg_ls_* to show file type and show special files

---
 src/backend/utils/adt/genfile.c              | 52 ++++++++++++--------
 src/include/catalog/pg_proc.dat              | 38 +++++++-------
 src/test/regress/expected/misc_functions.out | 40 +++++++--------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +--
 5 files changed, 77 insertions(+), 67 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4d90f37a21..1fdb2bf280 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -38,7 +38,7 @@
 
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -53,7 +53,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -367,6 +367,26 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+	return '?';
+}
+
 /*
  * stat a file
  */
@@ -414,7 +434,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
@@ -430,12 +450,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
+	values[5] = CharGetDatum(get_file_type(fst.st_mode, filename));
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -501,10 +516,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -631,12 +646,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			nulls[4] = true;
 			values[5] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_ctime));
 #endif
-			values[6] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-			/* Links should have isdir=false */
-			if (pgwin32_is_junction(path))
-				values[6] = BoolGetDatum(false);
-#endif
+			values[6] = CharGetDatum(get_file_type(attrib.st_mode, path));
 		}
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -718,7 +728,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_TYPE);
 }
 
 /*
@@ -733,5 +743,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d97ff8097f..8f19220456 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6107,16 +6107,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10777,13 +10777,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10793,32 +10793,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '5033', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '5034', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.type from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index cce84a60a9..1bdcca16fc 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,39 +222,39 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
- name | isdir 
-------+-------
- .    | t
+select name, type from pg_ls_dir_metadata('.') where name='.';
+ name | type 
+------+------
+ .    | d
 (1 row)
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
- name | isdir 
-------+-------
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | type 
+------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
-         name          | isdir 
------------------------+-------
- pg_wal                | t
- pg_wal/archive_status | t
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
+         name          | type 
+-----------------------+------
+ pg_wal                | d
+ pg_wal/archive_status | d
 (2 rows)
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 6041c4f3dc..3874625ca1 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
+select name, type from pg_ls_dir_metadata('.') where name='.';
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-04-12 11:53                             ` Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Fabien COELHO @ 2020-04-12 11:53 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>


Hello Justin,

About v15, seen as one patch.

Patches serie applies cleanly, compiles, "make check" ok.

Documentation:
  - indent documentation text around 80 cols, as done around?
  - indent SQL example for readability and capitalize keywords
    (pg_ls_dir_metadata)
  - "For each file in a directory, list the file and its metadata."
    maybe: "List files and their metadata in a directory"?

Code:
  - Most pg_ls_*dir* functions call pg_ls_dir_files(), which looks like
    reasonable refactoring, ISTM that the code is actually smaller.
  - please follow pg style, eg not "} else {"
  - there is a "XXX" (meaning fixme?) tag remaining in a comment.
  - file types: why not do block & character devices, fifo and socket
    as well, before the unkown case?
  - I'm wondering whether could pg_stat_file call pg_ls_dir_files without
    too much effort? ISTM that the output structure nearly the same. I do
    not like much having one function specialized for files and one for
    directories.

Tests:
  - good, there are some!
  - indent SQL code, eg by starting a new line on new clauses?
  - put comments on separate lines (I'm not against it on principle, I do
    that, but I do not think that it is done much in test files).

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-05-03 02:42                               ` Justin Pryzby <pryzby@telsasoft.com>
  2020-05-07 15:08                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-05-03 02:42 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Sun, Apr 12, 2020 at 01:53:40PM +0200, Fabien COELHO wrote:
> About v15, seen as one patch.

Thanks for looking.

> - I'm wondering whether could pg_stat_file call pg_ls_dir_files without
>   too much effort? ISTM that the output structure nearly the same. I do
>   not like much having one function specialized for files and one for
>   directories.

I refactored but not like that.  As I mentioned in the commit message, I don't
see a good way to make a function operate on a file when the function's primary
data structure is a DIR*.  Do you ?  I don't think it should call stat() and
then conditionally branch off to pg_stat_file().

There are two functions because they wrap two separate syscalls, which see as
good, transparent goal.  If we want a function that does what "ls -al" does,
that would also be a good example to follow, except that we already didn't
follow it.

/bin/ls first stat()s the path, and then either outputs its metadata (if it's a
file or if -d was specified) or lists a dir.  It's essentially a wrapper around
*two* system calls (stat and readdir/getdents).

Maybe we could invent a new pg_ls() which does that, and then refactor existing
code.  Or, maybe it would be a SQL function which calls stat() and then
conditionally calls pg_ls_dir if isdir=True (or type='d').  That would be easy
if we merge the commit which outputs all stat fields.

I'm still hoping for confirmation from a committer if this approach is worth
pursuing:

https://www.postgresql.org/message-id/20200310183037.GA29065%40telsasoft.com
https://www.postgresql.org/message-id/20200313131232.GO29065%40telsasoft.com
|Rather than making "recurse into tmpdir" the end goal:
|
|  - add a function to show metadata of an arbitrary dir;
|  - add isdir arguments to pg_ls_* functions (including pg_ls_tmpdir but not
|    pg_ls_dir).
|  - maybe add pg_ls_dir_recurse, which satisfies the original need;
|  - retire pg_ls_dir (does this work with tuplestore?)
|  - profit

-- 
Justin

Attachments:

  [text/x-diff] v16-0001-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20200503024215.GJ28974@telsasoft.com/2-v16-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 6175cecd312296bbb9099834d91ffaa50e059f6c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v16 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96ea57eedd..9b885102da 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25486,7 +25486,8 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory.  Typical usages include:
+    indicating if it is a directory (or a symbolic link to a directory).
+    Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
 SELECT (pg_stat_file('filename')).modification;
-- 
2.17.0

  [text/x-diff] v16-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.2K, ../../20200503024215.GJ28974@telsasoft.com/3-v16-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 52fd41bfacaba66f26f1e75b34e258b3a0f12372 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v16 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9b885102da..96b08d0500 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25486,7 +25486,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
     size, last accessed time stamp, last modified time stamp,
     last file status change time stamp (Unix platforms only),
     file creation time stamp (Windows only), and a <type>boolean</type>
-    indicating if it is a directory (or a symbolic link to a directory).
+    indicating if it is a directory.
     Typical usages include:
 <programlisting>
 SELECT * FROM pg_stat_file('filename');
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ceaa6180da..219ac160f8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -370,7 +370,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -596,7 +596,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v16-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200503024215.GJ28974@telsasoft.com/4-v16-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 66d83278898baf7451c4984ffb0090a08d898880 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v16 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v16-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.1K, ../../20200503024215.GJ28974@telsasoft.com/5-v16-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 71f1e41b20e232ade555dc63f3a6b15c31203d90 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  19 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 218 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         List the contents of a directory.  Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+        For each file in a directory, list the file and its metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     empty directory from an non-existent directory.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_metadata</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_metadata</function> lists the files in the specified
+    directory along with the file's metadata.
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir 
+------+-------
+ .    | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir 
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (7.4K, ../../20200503024215.GJ28974@telsasoft.com/6-v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 04ed9715a4920828d554770de2c3c48cff1cc548 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v16 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 22 +++++++++++---------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 +++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d617f95b9..891b990859 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25351,12 +25351,12 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the
-        temporary directory for <parameter>tablespace</parameter>.  If
-        <parameter>tablespace</parameter> is not provided, the
-        <literal>pg_default</literal> tablespace is used.  Access is granted
-        to members of the <literal>pg_monitor</literal> role and may be
-        granted to other non-superuser roles.
+        For the temporary directory within <parameter>tablespace</parameter>,
+        list each file's name, size, last modification time, and a boolean
+        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        is not provided, the <literal>pg_default</literal> tablespace is used.
+        Access is granted to members of the <literal>pg_monitor</literal> role
+        and may be granted to other non-superuser roles.
        </entry>
       </row>
       <row>
@@ -25459,14 +25459,16 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_tmpdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_tmpdir</function> returns the name, size, and last modified
-    time (mtime) of each file in the temporary file directory for the specified
-    <parameter>tablespace</parameter>.  If <parameter>tablespace</parameter> is
+    <function>pg_ls_tmpdir</function> lists each file in the temporary file
+    directory for the specified <parameter>tablespace</parameter>, along with
+    its size, last modified time (mtime) and a boolean indicating if the file is a
+    directory.  Directories are used for temporary files shared by parallel
+    processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
     role can use this function.  Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.4K, ../../20200503024215.GJ28974@telsasoft.com/7-v16-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 5ad5a708b1fe7183aff91eca86c29a9c216dac93 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v16 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 39 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 ++++--
 src/include/catalog/pg_proc.dat              | 12 +++---
 src/test/regress/expected/misc_functions.out |  4 +-
 4 files changed, 37 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 891b990859..c3a6b3277f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25317,8 +25317,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the log
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the log directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -25328,8 +25329,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        directory. Access is granted to members of the <literal>pg_monitor</literal>
+        For each file in the WAL directory, list the file's name, size, last
+        modification time, and a boolean indicating if it is a directory.
+        Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
       </row>
@@ -25339,8 +25341,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        List the name, size, and last modification time of files in the WAL
-        archive status directory. Access is granted to members of the
+        For each file in the WAL archive status directory, list the file's
+        name, size, last modification time, and a boolean indicating if it is a
+        directory.  Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -25423,36 +25426,38 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     <primary>pg_ls_logdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_logdir</function> returns the name, size, and last modified time
-    (mtime) of each file in the log directory. By default, only superusers
+    <function>pg_ls_logdir</function> lists each file in the log directory,
+    along with file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_waldir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_waldir</function> returns the name, size, and last modified time
-    (mtime) of each file in the write ahead log (WAL) directory. By
-    default only superusers and members of the <literal>pg_monitor</literal> role
+    <function>pg_ls_waldir</function> lists each file in the WAL directory,
+    along with the file's size, last modification time, and a boolean
+    indicating if the file is a directory.  By default, only superusers
+    and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
     <primary>pg_ls_archive_statusdir</primary>
    </indexterm>
    <para>
-    <function>pg_ls_archive_statusdir</function> returns the name, size, and
-    last modified time (mtime) of each file in the WAL archive status
-    directory <filename>pg_wal/archive_status</filename>. By default only
+    <function>pg_ls_archive_statusdir</function> lists each file in the WAL
+    archive status directory, along with the file's size, last modification
+    time, and a boolean indicating if the file is a directory.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
-    Filenames beginning with a dot, directories, and other special files are not shown.
+    Filenames beginning with a dot and special file types are not shown.
    </para>
 
    <indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 023f1ad605..fba63568b6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -644,14 +647,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -670,7 +673,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -700,7 +703,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index dc3b497587..adfce45d1a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10880,18 +10880,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 38493de732..64b1417fb8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v16-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (5.8K, ../../20200503024215.GJ28974@telsasoft.com/8-v16-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 44951d8c1d07df57806dab12dd77c155de9527ec Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v16 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 27 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 +++++
 src/test/regress/expected/misc_functions.out | 14 ++++++++++
 src/test/regress/sql/misc_functions.sql      |  6 +++++
 5 files changed, 54 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c3a6b3277f..3a2ad3eb50 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25311,6 +25311,18 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         EXECUTE to run the function.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_ls_dir_recurse(<parameter>dirname</parameter> <type>text</type>)</function></literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>
+        Call pg_ls_dir_metadata to recursively list the files in the specified
+        directory, along with each file's metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_ls_logdir()</function></literal>
@@ -25422,6 +25434,21 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
     directory along with the file's metadata.
    </para>
 
+   <indexterm>
+    <primary>pg_ls_dir_recurse</primary>
+   </indexterm>
+   <para>
+    <function>pg_ls_dir_recurse</function> recursively lists the files 
+    in the specified directory.
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para>
+
    <indexterm>
     <primary>pg_ls_logdir</primary>
    </indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1c77430f0c..215fba3b7d 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1468,6 +1468,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index adfce45d1a..f94f403475 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10916,6 +10916,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '5034', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 64b1417fb8..4188d684f0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,20 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 372345720d..6041c4f3dc 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v16-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20200503024215.GJ28974@telsasoft.com/9-v16-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 49fb4f0f6be210667fdd5d27aba26c5a9227fd09 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (18.7K, ../../20200503024215.GJ28974@telsasoft.com/10-v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 392dc661c9cc627acab82fd9a35b33061c925349 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v16 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 30 ++++-----
 src/backend/utils/adt/genfile.c              | 65 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 71 insertions(+), 82 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3a2ad3eb50..1177799916 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25329,8 +25329,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the log directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the log directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -25341,8 +25340,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL directory, list the file's name, size, last
-        modification time, and a boolean indicating if it is a directory.
+        For each file in the WAL directory, list the file and its metadata.
         Access is granted to members of the <literal>pg_monitor</literal>
         role and may be granted to other non-superuser roles.
        </entry>
@@ -25353,9 +25351,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </entry>
        <entry><type>setof record</type></entry>
        <entry>
-        For each file in the WAL archive status directory, list the file's
-        name, size, last modification time, and a boolean indicating if it is a
-        directory.  Access is granted to members of the
+        For each file in the WAL archive status directory, list the file and its metadata.
+        Access is granted to members of the
         <literal>pg_monitor</literal> role and may be granted to other
         non-superuser roles.
        </entry>
@@ -25367,8 +25364,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry><type>setof record</type></entry>
        <entry>
         For the temporary directory within <parameter>tablespace</parameter>,
-        list each file's name, size, last modification time, and a boolean
-        indicating if it is a directory.  If <parameter>tablespace</parameter>
+        list each file and its metadata.  If <parameter>tablespace</parameter>
         is not provided, the <literal>pg_default</literal> tablespace is used.
         Access is granted to members of the <literal>pg_monitor</literal> role
         and may be granted to other non-superuser roles.
@@ -25454,8 +25450,8 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_logdir</function> lists each file in the log directory,
-    along with file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role can use this function.
     Access may be granted to others using <command>GRANT</command>.
     Filenames beginning with a dot and special file types are not shown.
@@ -25466,8 +25462,8 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_waldir</function> lists each file in the WAL directory,
-    along with the file's size, last modification time, and a boolean
-    indicating if the file is a directory.  By default, only superusers
+    along with the metadata columns returned by <function>pg_stat_file</function>.
+    By default, only superusers
     and members of the <literal>pg_monitor</literal> role
     can use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -25479,8 +25475,8 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
    </indexterm>
    <para>
     <function>pg_ls_archive_statusdir</function> lists each file in the WAL
-    archive status directory, along with the file's size, last modification
-    time, and a boolean indicating if the file is a directory.  By default, only 
+    archive status directory, along with the metadata columns returned by
+    <function>pg_stat_file</function>.  By default, only 
     superusers and members of the <literal>pg_monitor</literal> role can
     use this function. Access may be granted to others using
     <command>GRANT</command>.
@@ -25493,8 +25489,8 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
    <para>
     <function>pg_ls_tmpdir</function> lists each file in the temporary file
     directory for the specified <parameter>tablespace</parameter>, along with
-    its size, last modified time (mtime) and a boolean indicating if the file is a
-    directory.  Directories are used for temporary files shared by parallel
+    the metadata columns returned by <function>pg_stat_file</function>.
+    Directories are used for temporary files shared by parallel
     processes.  If <parameter>tablespace</parameter> is
     not provided, the <literal>pg_default</literal> tablespace is used.  By
     default only superusers and members of the <literal>pg_monitor</literal>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 91bf8c69e9..10780d3fb1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -367,6 +369,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and isnull from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * isnull is assumed to have been zeroed.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	isnull[4] = true;
+#else
+	isnull[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -418,25 +442,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
+	tuple_from_stat(&fst, filename, values, isnull);
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
 	pfree(filename);
@@ -578,8 +584,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -618,23 +624,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f94f403475..34ee5a8f97 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10880,48 +10880,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '5033', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,name,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '5034', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4188d684f0..cce84a60a9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
@@ -239,8 +239,8 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -253,8 +253,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v16-0010-pg_ls_-to-show-file-type-and-show-special-files.patch (17.5K, ../../20200503024215.GJ28974@telsasoft.com/11-v16-0010-pg_ls_-to-show-file-type-and-show-special-files.patch)
  download | inline diff:
From 90b2cdee7516a3ec358ad1f248f45352f782917e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v16 10/10] pg_ls_* to show file type and show special files

---
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 40 +++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 5 files changed, 94 insertions(+), 56 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 10780d3fb1..57b52625a0 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -55,7 +56,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -369,6 +370,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and isnull from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -388,7 +426,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -438,7 +476,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
@@ -507,10 +545,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -708,7 +746,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_TYPE);
 }
 
 /*
@@ -723,5 +761,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 34ee5a8f97..6e1682dafb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6158,16 +6158,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10880,13 +10880,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10896,32 +10896,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '5033', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '5034', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.type from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index cce84a60a9..1bdcca16fc 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,39 +222,39 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
- name | isdir 
-------+-------
- .    | t
+select name, type from pg_ls_dir_metadata('.') where name='.';
+ name | type 
+------+------
+ .    | d
 (1 row)
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
- name | isdir 
-------+-------
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | type 
+------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
-         name          | isdir 
------------------------+-------
- pg_wal                | t
- pg_wal/archive_status | t
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
+         name          | type 
+-----------------------+------
+ pg_wal                | d
+ pg_wal/archive_status | d
 (2 rows)
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 6041c4f3dc..3874625ca1 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
+select name, type from pg_ls_dir_metadata('.') where name='.';
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-05-07 15:08                                 ` Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 0 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-05-07 15:08 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

Rebased onto 1ad23335f36b07f4574906a8dc66a3d62af7c40c

Attachments:

  [text/x-diff] v17-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20200507150835.GB28974@telsasoft.com/2-v17-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 698026f6365a324bf52c5985d549f71b52ada567 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v17 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..e0d1eff6b5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25861,7 +25861,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v17-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20200507150835.GB28974@telsasoft.com/3-v17-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From ecd5654cb1b22254387d3584bbc28cb1af046a62 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v17 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e0d1eff6b5..d9b3598977 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25861,7 +25861,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ceaa6180da..219ac160f8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -370,7 +370,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -596,7 +596,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v17-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200507150835.GB28974@telsasoft.com/4-v17-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From c5b407dd179019a9b79c29f921f1f2366c7d52ed Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v17 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v17-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20200507150835.GB28974@telsasoft.com/5-v17-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From ea199a4b9bdbbc22099882baec0b3b9b820ba6d4 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 220 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir 
+------+-------
+ .    | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir 
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20200507150835.GB28974@telsasoft.com/6-v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 89f5b4d430aa92e9c7a4d89d16c14b65602bcdaf Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v17 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc1b4ac98c..c9d1857182 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return each file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9f46cba5ed..dc3b497587 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10896,13 +10896,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.4K, ../../20200507150835.GB28974@telsasoft.com/7-v17-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From b2072da46c360ed561744feba4d391ae921cbb2e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v17 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 38 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 +++---
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 37 insertions(+), 28 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c9d1857182..8e0de7c02d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25700,12 +25700,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25725,7 +25727,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25744,13 +25747,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25768,13 +25772,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25798,7 +25804,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return each file's name, size, last
+        <parameter>tablespace</parameter>, list the file's name, size, last
         modification time (mtime) and a boolean indicating if the file is a directory.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 023f1ad605..fba63568b6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -644,14 +647,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -670,7 +673,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -700,7 +703,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index dc3b497587..adfce45d1a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10880,18 +10880,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 38493de732..64b1417fb8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v17-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (5.8K, ../../20200507150835.GB28974@telsasoft.com/8-v17-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From a8167956e79443fed5cc75e7a31462ac9d8867c8 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v17 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 32 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 ++++
 src/test/regress/expected/misc_functions.out | 14 +++++++++
 src/test/regress/sql/misc_functions.sql      |  6 ++++
 5 files changed, 59 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8e0de7c02d..334b6beb3a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25738,6 +25738,38 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_recurse</primary>
+        </indexterm>
+        <function>pg_ls_dir_recurse</function> ( <parameter>dirname</parameter> <type>text</type> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
+       </para>
+       <para>
+        Recursively list each file in the specified directory, along with the
+        files' metadata.
+       </para>
+       <para>
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
+   <!--para>
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para-->
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 1c77430f0c..215fba3b7d 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1468,6 +1468,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index adfce45d1a..f94f403475 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10916,6 +10916,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '5034', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 64b1417fb8..4188d684f0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,20 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 372345720d..6041c4f3dc 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v17-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20200507150835.GB28974@telsasoft.com/9-v17-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 055989d2a2bd1cc4eee7e83e24b76d6cf08aeb14 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (19.6K, ../../20200507150835.GB28974@telsasoft.com/10-v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From fb4d2af42f0fc74916a6a1857c2faa3d1e3cc266 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v17 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++---
 src/backend/utils/adt/genfile.c              | 65 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 85 insertions(+), 74 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 334b6beb3a..c3ec56a5f6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25700,13 +25700,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25727,7 +25730,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25747,7 +25753,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25779,13 +25788,15 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        filename along with the metadata columns returned by <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25804,14 +25815,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the filename
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25831,13 +25845,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, list the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 91bf8c69e9..10780d3fb1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -367,6 +369,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and isnull from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * isnull is assumed to have been zeroed.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	isnull[4] = true;
+#else
+	isnull[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -418,25 +442,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
+	tuple_from_stat(&fst, filename, values, isnull);
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
 	pfree(filename);
@@ -578,8 +584,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -618,23 +624,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f94f403475..34ee5a8f97 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10880,48 +10880,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '5033', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,name,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '5034', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4188d684f0..cce84a60a9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
@@ -239,8 +239,8 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -253,8 +253,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v17-0010-pg_ls_-to-show-file-type-and-show-special-files.patch (19.6K, ../../20200507150835.GB28974@telsasoft.com/11-v17-0010-pg_ls_-to-show-file-type-and-show-special-files.patch)
  download | inline diff:
From 27d5e532ba865225cdaddf88e2d612d7b15dd054 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v17 10/10] pg_ls_* to show file type and show special files

---
 doc/src/sgml/func.sgml                       |  8 +--
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 40 +++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 6 files changed, 98 insertions(+), 60 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c3ec56a5f6..e7d4aa57ec 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25710,7 +25710,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25797,7 +25797,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         filename along with the metadata columns returned by <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25826,7 +25826,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         (<filename>pg_wal/archive_status</filename>), list the filename
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25859,7 +25859,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 10780d3fb1..f0af513249 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -55,7 +56,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -369,6 +370,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and isnull from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -388,7 +426,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -438,7 +476,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
@@ -507,10 +545,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -708,7 +746,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -723,5 +761,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 34ee5a8f97..6e1682dafb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6158,16 +6158,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10880,13 +10880,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10896,32 +10896,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '5032', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '5033', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '5034', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.type from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false)a where ls.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index cce84a60a9..1bdcca16fc 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,39 +222,39 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
- name | isdir 
-------+-------
- .    | t
+select name, type from pg_ls_dir_metadata('.') where name='.';
+ name | type 
+------+------
+ .    | d
 (1 row)
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
- name | isdir 
-------+-------
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | type 
+------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
-         name          | isdir 
------------------------+-------
- pg_wal                | t
- pg_wal/archive_status | t
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
+         name          | type 
+-----------------------+------
+ pg_wal                | d
+ pg_wal/archive_status | d
 (2 rows)
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 6041c4f3dc..3874625ca1 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
+select name, type from pg_ls_dir_metadata('.') where name='.';
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-05-26 02:10                                 ` Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-05-26 02:10 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

Rebased onto 7b48f1b490978a8abca61e9a9380f8de2a56f266 and renumbered OIDs.

Attachments:

  [text/x-diff] v18-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20200526021003.GW4472@telsasoft.com/2-v18-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From bb41ae268041b7e7771930d533a8ca20a00805c7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v18 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c06afd3ea..11f5ef9372 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25861,7 +25861,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v18-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20200526021003.GW4472@telsasoft.com/3-v18-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From af4275ac21bf6699b5265b4d80f02de7a8e6cb9f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v18 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 11f5ef9372..7c06afd3ea 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25861,7 +25861,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ceaa6180da..219ac160f8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -370,7 +370,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -596,7 +596,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v18-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200526021003.GW4472@telsasoft.com/4-v18-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From c122fc6e1d80beac33478a52fd7bdc6835fde317 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v18 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v18-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20200526021003.GW4472@telsasoft.com/5-v18-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 0e7782f6a11db955b2577825d5b3598747779691 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v18 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 220 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c06afd3ea..8ea064144f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 56420bbc9d..cc8e870740 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1468,6 +1468,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61f2c2f5b4..5b0147ee2d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10902,6 +10902,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir 
+------+-------
+ .    | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir 
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v18-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20200526021003.GW4472@telsasoft.com/6-v18-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 9a72a83537b3a92aefd8822daef3002f53199fdb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v18 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8ea064144f..33f7137e43 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,16 +25793,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return each file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5b0147ee2d..08c7189470 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10894,13 +10894,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v18-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.4K, ../../20200526021003.GW4472@telsasoft.com/7-v18-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 623966e828bebb5ffce48144bee84356da967a0d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v18 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 38 +++++++++++---------
 src/backend/utils/adt/genfile.c              | 11 +++---
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 37 insertions(+), 28 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 33f7137e43..4aa9cdb18c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25700,12 +25700,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25725,7 +25727,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25744,13 +25747,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25768,13 +25772,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25798,7 +25804,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return each file's name, size, last
+        <parameter>tablespace</parameter>, list the file's name, size, last
         modification time (mtime) and a boolean indicating if the file is a directory.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 023f1ad605..fba63568b6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -644,14 +647,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -670,7 +673,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -700,7 +703,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 08c7189470..b61f30247e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10878,18 +10878,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 38493de732..64b1417fb8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v18-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (5.8K, ../../20200526021003.GW4472@telsasoft.com/8-v18-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 486888b45403133067a5eeebd27539f6f8371121 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v18 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 32 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 ++++
 src/test/regress/expected/misc_functions.out | 14 +++++++++
 src/test/regress/sql/misc_functions.sql      |  6 ++++
 5 files changed, 59 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4aa9cdb18c..683f533392 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25738,6 +25738,38 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_recurse</primary>
+        </indexterm>
+        <function>pg_ls_dir_recurse</function> ( <parameter>dirname</parameter> <type>text</type> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
+       </para>
+       <para>
+        Recursively list each file in the specified directory, along with the
+        files' metadata.
+       </para>
+       <para>
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
+   <!--para>
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para-->
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index cc8e870740..58d535a9f6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1469,6 +1469,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b61f30247e..bac1226db7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10914,6 +10914,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 64b1417fb8..4188d684f0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,20 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 372345720d..6041c4f3dc 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v18-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20200526021003.GW4472@telsasoft.com/9-v18-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 4945ddd40c1fac30c90d7ad15aa45da60f034b0c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v18 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v18-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (19.6K, ../../20200526021003.GW4472@telsasoft.com/10-v18-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 88c3479f55e19005d15bf479e0eba92f32f429ce Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v18 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++---
 src/backend/utils/adt/genfile.c              | 65 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 85 insertions(+), 74 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 683f533392..00ec9b213c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25700,13 +25700,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25727,7 +25730,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25747,7 +25753,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25779,13 +25788,15 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        filename along with the metadata columns returned by <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25804,14 +25815,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the filename
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25831,13 +25845,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, list the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 91bf8c69e9..10780d3fb1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -367,6 +369,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and isnull from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * isnull is assumed to have been zeroed.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	isnull[4] = true;
+#else
+	isnull[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -418,25 +442,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
+	tuple_from_stat(&fst, filename, values, isnull);
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
 	pfree(filename);
@@ -578,8 +584,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -618,23 +624,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bac1226db7..c48c0ca2c8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10878,48 +10878,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,name,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4188d684f0..cce84a60a9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
@@ -239,8 +239,8 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -253,8 +253,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v18-0010-pg_ls_-to-show-file-type-and-show-special-files.patch (19.6K, ../../20200526021003.GW4472@telsasoft.com/11-v18-0010-pg_ls_-to-show-file-type-and-show-special-files.patch)
  download | inline diff:
From 36fd244cf969d4dba63a57766a4b58cb9352ddc7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v18 10/10] pg_ls_* to show file type and show special files

---
 doc/src/sgml/func.sgml                       |  8 +--
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 40 +++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 6 files changed, 98 insertions(+), 60 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 00ec9b213c..5d4bcf5fff 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25710,7 +25710,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25797,7 +25797,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         filename along with the metadata columns returned by <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25826,7 +25826,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         (<filename>pg_wal/archive_status</filename>), list the filename
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25859,7 +25859,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 10780d3fb1..f0af513249 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -55,7 +56,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -369,6 +370,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and isnull from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -388,7 +426,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -438,7 +476,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
@@ -507,10 +545,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -708,7 +746,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -723,5 +761,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c48c0ca2c8..39d9029fa6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6156,16 +6156,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10878,13 +10878,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10894,32 +10894,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.type from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index cce84a60a9..1bdcca16fc 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,39 +222,39 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
- name | isdir 
-------+-------
- .    | t
+select name, type from pg_ls_dir_metadata('.') where name='.';
+ name | type 
+------+------
+ .    | d
 (1 row)
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
- name | isdir 
-------+-------
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | type 
+------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
-         name          | isdir 
------------------------+-------
- pg_wal                | t
- pg_wal/archive_status | t
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
+         name          | type 
+-----------------------+------
+ pg_wal                | d
+ pg_wal/archive_status | d
 (2 rows)
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 6041c4f3dc..3874625ca1 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
+select name, type from pg_ls_dir_metadata('.') where name='.';
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-06-07 08:07                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Fabien COELHO @ 2020-06-07 08:07 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>


Hello Justin,

> Rebased onto 7b48f1b490978a8abca61e9a9380f8de2a56f266 and renumbered OIDs.

Some feedback about v18, seen as one patch.

Patch applies cleanly & compiles. "make check" is okay.

pg_stat_file() and pg_stat_dir_files() now return a char type, as well as 
the function which call them, but the documentation does not seem to say 
that it is the case.

I must admit that I'm not a fan on the argument management of 
pg_ls_dir_metadata and pg_ls_dir_metadata_1arg and others. I understand 
that it saves a few lines though, so maybe let it be.

There is a comment in pg_ls_dir_files which talks about pg_ls_dir.

Could pg_ls_*dir functions C implementations be dropped in favor of a pure 
SQL implementation, like you did with recurse?

If so, ISTM that pg_ls_dir_files() could be significantly simplified by 
moving its filtering flag to SQL conditions on "type" and others. That 
could allow not to change the existing function output a keep the "isdir" 
column defined as "type = 'd'" where it was used previously, if someone 
complains, but still have the full capability of "ls". That would also 
allow to drop the "*_1arg" hacks. Basically I'm advocating having 1 or 2 
actual C functions, and all other variants managed at the SQL level.

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2020-06-22 01:53                                     ` Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-06-22 01:53 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Sun, Jun 07, 2020 at 10:07:19AM +0200, Fabien COELHO wrote:
> Hello Justin,
> > Rebased onto 7b48f1b490978a8abca61e9a9380f8de2a56f266 and renumbered OIDs.

Rebased again on whatever broke func.sgml.

> pg_stat_file() and pg_stat_dir_files() now return a char type, as well as
> the function which call them, but the documentation does not seem to say
> that it is the case.

Fixed, thanks

> I must admit that I'm not a fan on the argument management of
> pg_ls_dir_metadata and pg_ls_dir_metadata_1arg and others. I understand that
> it saves a few lines though, so maybe let it be.

I think you're saying that you don't like the _1arg functions, but they're
needed to allow the regression tests to pass:

| * note: this wrapper is necessary to pass the sanity check in opr_sanity,
| * which checks that all built-in functions that share the implementing C
| * function take the same number of arguments

> There is a comment in pg_ls_dir_files which talks about pg_ls_dir.
> 
> Could pg_ls_*dir functions C implementations be dropped in favor of a pure
> SQL implementation, like you did with recurse?

I'm still waiting to hear feedback from a commiter if this is a good idea to
put this into the system catalog.  Right now, ts_debug is the only nontrivial
function.

> If so, ISTM that pg_ls_dir_files() could be significantly simplified by
> moving its filtering flag to SQL conditions on "type" and others. That could
> allow not to change the existing function output a keep the "isdir" column
> defined as "type = 'd'" where it was used previously, if someone complains,
> but still have the full capability of "ls". That would also allow to drop
> the "*_1arg" hacks. Basically I'm advocating having 1 or 2 actual C
> functions, and all other variants managed at the SQL level.

You want to get rid of the 1arg stuff and just have one function.

I see your point, but I guess the C function would still need to accept a
"missing_ok" argument, so we need two functions, so there's not much utility in
getting rid of the "include_dot_dirs" argument, which is there for consistency
with pg_ls_dir.

Conceivably we could 1) get rid of pg_ls_dir, and 2) get rid of the
include_dot_dirs argument and 3) maybe make "missing_ok" a required argument;
and, 4) get rid of the C wrapper functions, and replace with a bunch of stuff
like this:

SELECT name, size, access, modification, change, creation, type='d' AS isdir
FROM pg_ls_dir_metadata('pg_wal') WHERE substring(name,1,1)!='.' AND type!='d';

Where the defaults I changed in this patchset still remain to be discussed:
with or without metadata, hidden files, dotdirs.

As I'm still waiting for committer feedback on the first 10 patches, so not
intending to add more.

-- 
Justin

Attachments:

  [text/x-diff] v19-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20200622015325.GG17995@telsasoft.com/2-v19-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 60593b397f03ec840a97f8004d97177dec463752 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v19 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7c450ea29..9f47745c5a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25881,7 +25881,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v19-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20200622015325.GG17995@telsasoft.com/3-v19-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From c1acf58316869e176bc2b215e2545f9183f100e3 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v19 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9f47745c5a..b7c450ea29 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25881,7 +25881,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ceaa6180da..219ac160f8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -370,7 +370,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -596,7 +596,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v19-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200622015325.GG17995@telsasoft.com/4-v19-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From d12b2a0b132352168a0f18a254c8e85652ec80be Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v19 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v19-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20200622015325.GG17995@telsasoft.com/5-v19-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From a0d0a400a4327eb0386bb4059d6e3639c0b02fba Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v19 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 220 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7c450ea29..2931f9d7d5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25711,6 +25711,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5314e9348f..f5217a0d93 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1481,6 +1481,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61f2c2f5b4..5b0147ee2d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10902,6 +10902,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir 
+------+-------
+ .    | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir 
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v19-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20200622015325.GG17995@telsasoft.com/6-v19-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 3b36df2e8ecb26af8c4d5f3969fd34d8ff37dc24 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v19 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2931f9d7d5..24dc90a74d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25813,16 +25813,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4824a55480..023f1ad605 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -481,7 +481,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5b0147ee2d..08c7189470 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10894,13 +10894,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v19-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (8.8K, ../../20200622015325.GG17995@telsasoft.com/7-v19-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 8d48f16a1d774f627a954c9192dd0cde1b3550fe Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v19 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 11 +++---
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 36 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24dc90a74d..d1d2f868d8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25722,7 +25722,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25741,12 +25742,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25764,13 +25767,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25788,13 +25792,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 023f1ad605..fba63568b6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -644,14 +647,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -670,7 +673,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -700,7 +703,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 08c7189470..b61f30247e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10878,18 +10878,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 38493de732..64b1417fb8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v19-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (5.8K, ../../20200622015325.GG17995@telsasoft.com/8-v19-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 32f700e80b199a107db1f69234d491c436814091 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v19 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 32 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 ++++
 src/test/regress/expected/misc_functions.out | 14 +++++++++
 src/test/regress/sql/misc_functions.sql      |  6 ++++
 5 files changed, 59 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d1d2f868d8..c097977d9b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25733,6 +25733,38 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_recurse</primary>
+        </indexterm>
+        <function>pg_ls_dir_recurse</function> ( <parameter>dirname</parameter> <type>text</type> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
+       </para>
+       <para>
+        Recursively list each file in the specified directory, along with the
+        files' metadata.
+       </para>
+       <para>
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
+   <!--para>
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para-->
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f5217a0d93..297b0dcdb8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1482,6 +1482,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b61f30247e..bac1226db7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10914,6 +10914,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 64b1417fb8..4188d684f0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,20 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+         name          | isdir 
+-----------------------+-------
+ pg_wal                | t
+ pg_wal/archive_status | t
+(2 rows)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 372345720d..6041c4f3dc 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v19-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20200622015325.GG17995@telsasoft.com/9-v19-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 0cf3d7e375cbdbe8eee4de7a3a07252233d55b59 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v19 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v19-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (19.5K, ../../20200622015325.GG17995@telsasoft.com/10-v19-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From a52ebb68737747bc0fe8292f0289021ec04c13f7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v19 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 37 ++++++++---
 src/backend/utils/adt/genfile.c              | 65 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 86 insertions(+), 74 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c097977d9b..cee8f28ef7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25722,7 +25722,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25742,7 +25745,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25774,13 +25780,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25799,13 +25808,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+	file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25824,14 +25836,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25851,13 +25866,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 91bf8c69e9..10780d3fb1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -367,6 +369,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and isnull from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * isnull is assumed to have been zeroed.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	isnull[4] = true;
+#else
+	isnull[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -418,25 +442,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
+	tuple_from_stat(&fst, filename, values, isnull);
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
 	pfree(filename);
@@ -578,8 +584,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -618,23 +624,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bac1226db7..c48c0ca2c8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10878,48 +10878,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,name,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.modification, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4188d684f0..cce84a60a9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
@@ -239,8 +239,8 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -253,8 +253,8 @@ SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v19-0010-pg_ls_-to-show-file-type-and-show-special-files.patch (20.8K, ../../20200622015325.GG17995@telsasoft.com/11-v19-0010-pg_ls_-to-show-file-type-and-show-special-files.patch)
  download | inline diff:
From 214c346bca7474d3b55c82553f6da5553c6fca02 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v19 10/10] pg_ls_* to show file type and show special files

---
 doc/src/sgml/func.sgml                       | 18 +++---
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 40 +++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 6 files changed, 104 insertions(+), 64 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index cee8f28ef7..8be10c826a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25790,7 +25790,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25816,9 +25816,9 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-	file's name along with the metadata columns returned by
+        file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25847,7 +25847,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25880,7 +25880,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25954,13 +25954,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 10780d3fb1..f0af513249 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -55,7 +56,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -369,6 +370,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and isnull from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -388,7 +426,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -438,7 +476,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
@@ -507,10 +545,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -708,7 +746,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -723,5 +761,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c48c0ca2c8..39d9029fa6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6156,16 +6156,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10878,13 +10878,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10894,32 +10894,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.isdir) select * from ls" },
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select * from pg_ls_dir_metadata(dirname, true, false) union all select ls.name||'/'||a.name, a.size, a.access, a.modification, a.change, a.creation, a.type from ls, lateral pg_ls_dir_metadata(dirname||'/'||ls.name, false, false) as a where ls.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index cce84a60a9..1bdcca16fc 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,39 +222,39 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
- name | isdir 
-------+-------
- .    | t
+select name, type from pg_ls_dir_metadata('.') where name='.';
+ name | type 
+------+------
+ .    | d
 (1 row)
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
- name | isdir 
-------+-------
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | type 
+------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
-         name          | isdir 
------------------------+-------
- pg_wal                | t
- pg_wal/archive_status | t
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
+         name          | type 
+-----------------------+------
+ pg_wal                | d
+ pg_wal/archive_status | d
 (2 rows)
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 6041c4f3dc..3874625ca1 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
+select name, type from pg_ls_dir_metadata('.') where name='.';
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND name~'^pg_wal';
+SELECT name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND name~'^pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-07-15 03:08                                       ` Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-07-15 03:08 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Sun, Jun 21, 2020 at 08:53:25PM -0500, Justin Pryzby wrote:
> I'm still waiting to hear feedback from a commiter if this is a good idea to
> put this into the system catalog.  Right now, ts_debug is the only nontrivial
> function.

I'm still missing feedback from committers about the foundation of this
approach.

But I finally looked into the pg_rewind test failure 

That led met to keep the "dir" as a separate column, since that's what's needed
there, and it's more convenient to have a separate column than to provide a
column needing to be parsed.

-- 
Justin

Attachments:

  [text/x-diff] v20-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20200715030839.GX23581@telsasoft.com/2-v20-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 29846d21841a76b7ebfbf5dfbeef36963bff545b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v20 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 959f6a1c2f..7ef8c7a847 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25911,7 +25911,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v20-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20200715030839.GX23581@telsasoft.com/3-v20-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 971d6165400ee85fb0ec4c19ad36a8e1146cc382 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v20 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7ef8c7a847..959f6a1c2f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25911,7 +25911,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c1cc19d1f5..46d0977c2e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -406,7 +406,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v20-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200715030839.GX23581@telsasoft.com/4-v20-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 75c95441077969bcb6d0151de4a9224697442a23 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v20 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v20-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20200715030839.GX23581@telsasoft.com/5-v20-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 9522a46dc77bc52843bf7b3b26a56791a76e9f1f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v20 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 220 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 959f6a1c2f..eead88419f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25741,6 +25741,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b6d35c2d11..360b6bda26 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1478,6 +1478,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 46d0977c2e..3324ffe16a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 95604e988a..186ced4a35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10919,6 +10919,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,name,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir 
+------+-------
+ .    | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir 
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v20-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20200715030839.GX23581@telsasoft.com/6-v20-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 53c59a7d853630d06bad6f5b06c403ef5240a1f6 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v20 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eead88419f..3c419672fc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25843,16 +25843,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3324ffe16a..8390626e49 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 186ced4a35..381f2e196f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10911,13 +10911,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7930909f02..38493de732 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v20-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (8.8K, ../../20200715030839.GX23581@telsasoft.com/7-v20-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 4917be2bda2b4c1f622b3e7f93af9c3bd5e31d7e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v20 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 11 +++---
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 36 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3c419672fc..9652409581 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25752,7 +25752,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25771,12 +25772,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25794,13 +25797,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25818,13 +25822,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 8390626e49..a4d4782c8c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -680,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -706,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +739,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 381f2e196f..0b5716525c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10895,18 +10895,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 38493de732..64b1417fb8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v20-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (5.9K, ../../20200715030839.GX23581@telsasoft.com/8-v20-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 2b41a0ee2cceb98307a67e0ffcfe50cb6b33ac71 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v20 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 32 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/include/catalog/pg_proc.dat              |  6 ++++
 src/test/regress/expected/misc_functions.out | 13 ++++++++
 src/test/regress/sql/misc_functions.sql      |  6 ++++
 5 files changed, 58 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9652409581..befd2ffca5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25741,6 +25741,38 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_recurse</primary>
+        </indexterm>
+        <function>pg_ls_dir_recurse</function> ( <parameter>dirname</parameter> <type>text</type> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
+       </para>
+       <para>
+        Recursively list each file in the specified directory, along with the
+        files' metadata.
+       </para>
+       <para>
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
+   <!--para>
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para-->
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 360b6bda26..44690be916 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1479,6 +1479,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0b5716525c..e60b6ca866 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10931,6 +10931,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,name,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,path,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, true, false) union all select parent.path||'/'||parent.name, a.name, a.size, a.modification, a.isdir from ls AS parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.name, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 64b1417fb8..07c0a7f961 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,19 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT path, name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND path='./pg_wal';
+   path   |      name      | isdir 
+----------+----------------+-------
+ ./pg_wal | archive_status | t
+(1 row)
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+ path | name | size | modification | isdir 
+------+------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 372345720d..349752da94 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+SELECT path, name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND path='./pg_wal';
+
+-- Check that expected columns are present
+SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v20-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20200715030839.GX23581@telsasoft.com/9-v20-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 43bff40d6b9e755e5bb61f7a1d67e85edb050a2b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v20 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a4d4782c8c..e1fb160124 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -683,7 +683,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v20-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (19.7K, ../../20200715030839.GX23581@telsasoft.com/10-v20-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From e7b479ab9edfda9efedda4b1f87de524c532e091 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v20 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 37 ++++++++---
 src/backend/utils/adt/genfile.c              | 65 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 86 insertions(+), 74 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index befd2ffca5..8c3e6f00dc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25750,7 +25750,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25784,7 +25787,10 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25804,13 +25810,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25829,13 +25838,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25854,14 +25866,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25881,13 +25896,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e1fb160124..0095ad9621 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -403,6 +405,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and isnull from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * isnull is assumed to have been zeroed.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	isnull[4] = true;
+#else
+	isnull[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -454,25 +478,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
+	tuple_from_stat(&fst, filename, values, isnull);
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
 	pfree(filename);
@@ -614,8 +620,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -654,23 +660,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e60b6ca866..e1a9cf1d0a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10895,48 +10895,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,name,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,path,name,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, true, false) union all select parent.path||'/'||parent.name, a.name, a.size, a.modification, a.isdir from ls AS parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.name, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,path,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, true, false) union all select parent.path||'/'||parent.name, a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.name, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 07c0a7f961..2880fca099 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select name, isdir from pg_ls_dir_metadata('.') where name='.';
@@ -239,8 +239,8 @@ select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; --
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -252,8 +252,8 @@ SELECT path, name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND path='./pg_
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- path | name | size | modification | isdir 
-------+------+------+--------------+-------
+ path | name | size | access | modification | change | creation | isdir 
+------+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v20-0010-pg_ls_-to-show-file-type-and-show-special-files.patch (24.5K, ../../20200715030839.GX23581@telsasoft.com/11-v20-0010-pg_ls_-to-show-file-type-and-show-special-files.patch)
  download | inline diff:
From 1cc981df2f4ec6816cfc77e72b374151d159efdd Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v20 10/10] pg_ls_* to show file type and show special files

---
 doc/src/sgml/func.sgml                       | 16 +++---
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/bin/pg_rewind/libpq_fetch.c              | 38 +++++--------
 src/bin/pg_rewind/t/RewindTest.pm            |  7 ++-
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 38 ++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 8 files changed, 123 insertions(+), 86 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8c3e6f00dc..2a51da872f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25820,7 +25820,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25848,7 +25848,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25877,7 +25877,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25910,7 +25910,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25984,13 +25984,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0095ad9621..fe142bcf86 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -55,7 +56,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -405,6 +406,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and isnull from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -424,7 +462,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -474,7 +512,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
@@ -543,10 +581,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -744,7 +782,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -759,5 +797,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index 1dbbceab0b..a662a5ce9b 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -176,31 +176,21 @@ libpqProcessFileList(void)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path ||'/'|| name, size, type,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
-		"                             AND oid::text = files.filename\n";
+		"FROM pg_ls_dir_recurse('.') files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = './pg_tblspc'\n"
+		"                             AND oid::text = files.name\n";
+// XXX: s/name/filename/
+// XXX: leading ./ is unfortunate
 	res = PQexec(conn, sql);
 
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
@@ -214,9 +204,9 @@ libpqProcessFileList(void)
 	/* Read result to local variables */
 	for (i = 0; i < PQntuples(res); i++)
 	{
-		char	   *path = PQgetvalue(res, i, 0);
+		char	   *path = 2+PQgetvalue(res, i, 0);
 		int64		filesize = atol(PQgetvalue(res, i, 1));
-		bool		isdir = (strcmp(PQgetvalue(res, i, 2), "t") == 0);
+		char		*typec = PQgetvalue(res, i, 2);
 		char	   *link_target = PQgetvalue(res, i, 3);
 		file_type_t type;
 
@@ -229,12 +219,14 @@ libpqProcessFileList(void)
 			continue;
 		}
 
-		if (link_target[0])
+		if (link_target[0] || *typec == 'l')
 			type = FILE_TYPE_SYMLINK;
-		else if (isdir)
+		else if (*typec == 'd')
 			type = FILE_TYPE_DIRECTORY;
-		else
+		else if (*typec == '-')
 			type = FILE_TYPE_REGULAR;
+		else
+			type = FILE_TYPE_REGULAR; // XXX
 
 		process_source_file(path, type, filesize, link_target);
 	}
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 7516af7a01..bc8dad074e 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,12 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool)
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_recurse(text)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e1a9cf1d0a..8e2d9bf4e5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6168,16 +6168,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10895,13 +10895,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10911,32 +10911,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,path,name,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, true, false) union all select parent.path||'/'||parent.name, a.name, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.name, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,name,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, true, false) union all select parent.path||'/'||parent.name, a.name, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.name, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2880fca099..9d562fcce1 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,38 +222,38 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
- name | isdir 
-------+-------
- .    | t
+select name, type from pg_ls_dir_metadata('.') where name='.';
+ name | type 
+------+------
+ .    | d
 (1 row)
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
- name | isdir 
-------+-------
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | type 
+------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT path, name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND path='./pg_wal';
-   path   |      name      | isdir 
-----------+----------------+-------
- ./pg_wal | archive_status | t
+SELECT path, name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND path='./pg_wal';
+   path   |      name      | type 
+----------+----------------+------
+ ./pg_wal | archive_status | d
 (1 row)
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
- path | name | size | access | modification | change | creation | isdir 
-------+------+------+--------+--------------+--------+----------+-------
+ path | name | size | access | modification | change | creation | type 
+------+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 349752da94..e7387760a6 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select name, isdir from pg_ls_dir_metadata('.') where name='.';
+select name, type from pg_ls_dir_metadata('.') where name='.';
 
-select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+select name, type from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-SELECT path, name, isdir FROM pg_ls_dir_recurse('.') WHERE isdir AND path='./pg_wal';
+SELECT path, name, type FROM pg_ls_dir_recurse('.') WHERE type='d' AND path='./pg_wal';
 
 -- Check that expected columns are present
 SELECT * FROM pg_ls_dir_recurse('.') LIMIT 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-07-18 20:15                                         ` Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-07-18 20:15 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Tue, Jul 14, 2020 at 10:08:39PM -0500, Justin Pryzby wrote:
> I'm still missing feedback from committers about the foundation of this
> approach.

Now rebased on top of fix for my own bug report (1d09fb1f).

I also changed argument handling for pg_ls_dir_recurse().

Passing '.' gave an initial path of . (of course) but then every other path
begins with './' which I didn't like, since it's ambiguous with empty path, or
.// or ././ ...  And one could pass './' which gives different output (like
././).  

So I specially handled the input of '.'.  Maybe the special value should be
NULL instead of ''.  But it looks no other system functions are currently
non-strict.

For pg_rewind testcase, getting the output path+filename uses a coalesce, since
the rest of the test does stuff like strcmp("pg_wal").

Still waiting for feedback from a committer.

-- 
Justin

Attachments:

  [text/x-diff] v21-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20200718201532.GV23581@telsasoft.com/2-v21-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From e8d75d56c4e3b54f1af0ffbcdeddb46872dc308b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v21 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 959f6a1c2f..7ef8c7a847 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25911,7 +25911,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v21-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20200718201532.GV23581@telsasoft.com/3-v21-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 8f9d4dbfd13faa22446dd5ae95f62aa10b82d6e1 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v21 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7ef8c7a847..959f6a1c2f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25911,7 +25911,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d34182a7b0..9f4927220b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -406,7 +406,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v21-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20200718201532.GV23581@telsasoft.com/4-v21-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 15f4638e42b496ac6b4064f439dd6a2ea862bf69 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v21 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v21-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20200718201532.GV23581@telsasoft.com/5-v21-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From e3a5d886c70725fca0aa0151d9c4f7bc1f69efe7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v21 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 220 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 959f6a1c2f..eead88419f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25741,6 +25741,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8625cbeab6..b9b2a6aa20 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1478,6 +1478,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..24d858d1bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 95604e988a..83927b510a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10919,6 +10919,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..20bfe71822 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..68e2bc6586 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v21-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20200718201532.GV23581@telsasoft.com/6-v21-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From fbf3b0b9671b70124582db1b40a5004b06868070 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v21 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eead88419f..3c419672fc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25843,16 +25843,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 24d858d1bc..cd7f052692 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 83927b510a..85ac8fef61 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10911,13 +10911,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 20bfe71822..0144f611c2 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v21-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (8.8K, ../../20200718201532.GV23581@telsasoft.com/7-v21-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 66b7224449b4d3a63b4d1949fd92aa3d7b7d8908 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v21 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 11 +++---
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 36 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3c419672fc..9652409581 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25752,7 +25752,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25771,12 +25772,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25794,13 +25797,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25818,13 +25822,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index cd7f052692..92e045e613 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -52,6 +52,9 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
  */
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
  *
@@ -680,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -706,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +739,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 85ac8fef61..58d1c74b52 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10895,18 +10895,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0144f611c2..bb926a2cf4 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v21-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (8.6K, ../../20200718201532.GV23581@telsasoft.com/8-v21-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 47413217c1a16ddd69ad351b838c3231693bb818 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v21 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 32 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/bin/pg_rewind/libpq_fetch.c              | 22 +++-----------
 src/bin/pg_rewind/t/RewindTest.pm            |  7 ++++-
 src/include/catalog/pg_proc.dat              |  6 ++++
 src/test/regress/expected/misc_functions.out | 13 ++++++++
 src/test/regress/sql/misc_functions.sql      |  6 ++++
 7 files changed, 69 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9652409581..befd2ffca5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25741,6 +25741,38 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_recurse</primary>
+        </indexterm>
+        <function>pg_ls_dir_recurse</function> ( <parameter>dirname</parameter> <type>text</type> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
+       </para>
+       <para>
+        Recursively list each file in the specified directory, along with the
+        files' metadata.
+       </para>
+       <para>
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
+   <!--para>
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para-->
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b9b2a6aa20..cdf105ffac 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1479,6 +1479,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index c44648f823..58f2bb9fd1 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -176,30 +176,18 @@ libpqProcessFileList(void)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_recurse('.') files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 7516af7a01..bc8dad074e 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,12 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool)
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_recurse(text)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 58d1c74b52..826a31c561 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10931,6 +10931,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,filename,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,path,filename,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.modification, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index bb926a2cf4..302f348a6d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,19 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ----------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+  path  |    filename    | isdir 
+--------+----------------+-------
+ pg_wal | archive_status | t
+(1 row)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_recurse('.') limit 0;
+ path | filename | size | modification | isdir 
+------+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 68e2bc6586..fda95828d7 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+
+-- Check that expected columns are present
+select * from pg_ls_dir_recurse('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v21-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20200718201532.GV23581@telsasoft.com/9-v21-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 13002fa7c88960d29526f74bccb05fb0f3a7c8dd Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v21 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 92e045e613..16a929f5bb 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -683,7 +683,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v21-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (19.8K, ../../20200718201532.GV23581@telsasoft.com/10-v21-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From e92ef79e8d0230a6574ba4eafe5104c2ec551b8b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v21 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 37 ++++++++---
 src/backend/utils/adt/genfile.c              | 65 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 86 insertions(+), 74 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index befd2ffca5..8c3e6f00dc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25750,7 +25750,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25784,7 +25787,10 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25804,13 +25810,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25829,13 +25838,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25854,14 +25866,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25881,13 +25896,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 16a929f5bb..d2f79d4ba9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -403,6 +405,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and isnull from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * isnull is assumed to have been zeroed.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	isnull[4] = true;
+#else
+	isnull[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -454,25 +478,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
+	tuple_from_stat(&fst, filename, values, isnull);
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
 	pfree(filename);
@@ -614,8 +620,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -654,23 +660,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, 0, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 826a31c561..dfb2e7717e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10895,48 +10895,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,path,filename,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.modification, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 302f348a6d..4be27fe21e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -239,8 +239,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -252,8 +252,8 @@ select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='p
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | modification | isdir 
-------+----------+------+--------------+-------
+ path | filename | size | access | modification | change | creation | isdir 
+------+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v21-0010-pg_ls_-to-show-file-type-and-show-special-files.patch (21.7K, ../../20200718201532.GV23581@telsasoft.com/11-v21-0010-pg_ls_-to-show-file-type-and-show-special-files.patch)
  download | inline diff:
From 6be3010a02fc6359b780c13a0906cd007fb893e2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v21 10/10] pg_ls_* to show file type and show special files

---
 doc/src/sgml/func.sgml                       | 16 +++---
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/bin/pg_rewind/libpq_fetch.c              |  2 +-
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 38 ++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 7 files changed, 103 insertions(+), 63 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8c3e6f00dc..2a51da872f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25820,7 +25820,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25848,7 +25848,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25877,7 +25877,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25910,7 +25910,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25984,13 +25984,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d2f79d4ba9..53ea15d0f1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *isnull);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -55,7 +56,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN|LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -405,6 +406,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and isnull from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -424,7 +462,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *isnull)
 	isnull[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -474,7 +512,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(isnull, false, sizeof(isnull));
@@ -543,10 +581,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -744,7 +782,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -759,5 +797,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index 58f2bb9fd1..62aaf6714e 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -184,7 +184,7 @@ libpqProcessFileList(void)
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
+		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
 		"FROM pg_ls_dir_recurse('.') files\n"
 		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index dfb2e7717e..b353f30ed0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6168,16 +6168,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10895,13 +10895,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10911,32 +10911,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4be27fe21e..9bfc445d99 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,38 +222,38 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
-  path  |    filename    | isdir 
---------+----------------+-------
- pg_wal | archive_status | t
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
+  path  |    filename    | type 
+--------+----------------+------
+ pg_wal | archive_status | d
 (1 row)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | access | modification | change | creation | isdir 
-------+----------+------+--------+--------------+--------+----------+-------
+ path | filename | size | access | modification | change | creation | type 
+------+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index fda95828d7..b87965a1bd 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-10-28 19:34                                           ` Justin Pryzby <pryzby@telsasoft.com>
  2020-11-05 13:51                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-10-28 19:34 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Tue, Sep 08, 2020 at 02:51:26PM -0500, Justin Pryzby wrote:
> On Sat, Jul 18, 2020 at 03:15:32PM -0500, Justin Pryzby wrote:
> > Still waiting for feedback from a committer.
> 
> This patch has been waiting for input from a committer on the approach I've
> taken with the patches since March 10, so I'm planning to set to "Ready" - at
> least ready for senior review.

@cfbot: rebased

Attachments:

  [text/x-diff] v22-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20201028193402.GA28528@telsasoft.com/2-v22-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 2b56825516d4107e16946b4e25084e5cdf85ba18 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v22 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7ef2ec9972..1b67ef4be8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25917,7 +25917,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v22-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20201028193402.GA28528@telsasoft.com/3-v22-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From b50a250527cd4979f58d3f7af23ea69cbd200a0e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v22 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1b67ef4be8..7ef2ec9972 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25917,7 +25917,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d34182a7b0..9f4927220b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -406,7 +406,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v22-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20201028193402.GA28528@telsasoft.com/4-v22-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 55f30341dfef59ca3015535b9ae62a7a40be083b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v22 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v22-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20201028193402.GA28528@telsasoft.com/5-v22-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 5e3757a2d733a4660a654a04d24559605cde464f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v22 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 220 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7ef2ec9972..34c92903d8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25747,6 +25747,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c6dd084fbc..169602c1b6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1509,6 +1509,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..054b8d4b1a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 24ec2cfed6..02b1436d83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10964,6 +10964,18 @@
   proallargtypes => '{regclass,text,text,int8}', proargmodes => '{i,i,o,o}',
   proargnames => '{relation,fork,path,failed_block_num}',
   prosrc => 'pg_relation_check_pages' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..20bfe71822 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..68e2bc6586 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v22-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20201028193402.GA28528@telsasoft.com/6-v22-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 6316059c7fc81b1d7bc93e076cd60c572b4e2c30 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v22 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 34c92903d8..4308a50ab2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25849,16 +25849,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 054b8d4b1a..454348a5e3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 02b1436d83..149dfacf4b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10949,13 +10949,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9147', descr => 'check pages of a relation',
   proname => 'pg_relation_check_pages', procost => '10000', prorows => '20',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 20bfe71822..0144f611c2 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v22-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20201028193402.GA28528@telsasoft.com/7-v22-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 6ba1a38e3357ff92836a27bf2027741950ac69c0 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v22 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4308a50ab2..f7ccf68ff4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25758,7 +25758,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25777,12 +25778,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25800,13 +25803,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25824,13 +25828,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 454348a5e3..a61ddfc451 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata.
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -680,14 +677,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -706,7 +703,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +733,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 149dfacf4b..c3564168e5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10933,18 +10933,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0144f611c2..bb926a2cf4 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v22-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (8.6K, ../../20201028193402.GA28528@telsasoft.com/8-v22-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 90a05ebfefa3cbeb54ae2a32b8529a287709ea6c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v22 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 32 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/bin/pg_rewind/libpq_fetch.c              | 22 +++-----------
 src/bin/pg_rewind/t/RewindTest.pm            |  7 ++++-
 src/include/catalog/pg_proc.dat              |  6 ++++
 src/test/regress/expected/misc_functions.out | 13 ++++++++
 src/test/regress/sql/misc_functions.sql      |  6 ++++
 7 files changed, 69 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f7ccf68ff4..7baf2b31e5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25769,6 +25769,38 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_recurse</primary>
+        </indexterm>
+        <function>pg_ls_dir_recurse</function> ( <parameter>dirname</parameter> <type>text</type> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
+       </para>
+       <para>
+        Recursively list each file in the specified directory, along with the
+        files' metadata.
+       </para>
+       <para>
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
+   <!--para>
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para-->
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 169602c1b6..55b700dadc 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1510,6 +1510,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index bf4dfc23b9..a9535d3dd5 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -176,30 +176,18 @@ libpqProcessFileList(void)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_recurse('.') files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 41ed7d4b3b..cacd58651c 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,12 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool)
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_recurse(text)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c3564168e5..0235042e26 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10976,6 +10976,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,filename,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,path,filename,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.modification, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index bb926a2cf4..302f348a6d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,19 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ----------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+  path  |    filename    | isdir 
+--------+----------------+-------
+ pg_wal | archive_status | t
+(1 row)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_recurse('.') limit 0;
+ path | filename | size | modification | isdir 
+------+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 68e2bc6586..fda95828d7 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+
+-- Check that expected columns are present
+select * from pg_ls_dir_recurse('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v22-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20201028193402.GA28528@telsasoft.com/9-v22-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From a4a04eb44685b6cddfbc56fb19f37ee23bbd7601 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v22 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a61ddfc451..35fe4d7cc1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v22-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (20.3K, ../../20201028193402.GA28528@telsasoft.com/10-v22-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 207a7c30a257ad5b8eada5384356fdc19ff16e39 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v22 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 37 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 89 insertions(+), 77 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7baf2b31e5..809483feed 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25758,7 +25758,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25778,7 +25781,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25810,13 +25816,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25835,13 +25844,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25860,14 +25872,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25887,13 +25902,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 35fe4d7cc1..20b948b746 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -397,6 +399,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -407,7 +431,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -447,27 +471,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	tuple_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -608,8 +614,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -648,23 +654,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0235042e26..3f62aea9c9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10933,29 +10933,29 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9147', descr => 'check pages of a relation',
   proname => 'pg_relation_check_pages', procost => '10000', prorows => '20',
@@ -10967,21 +10967,21 @@
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,path,filename,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.modification, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 302f348a6d..4be27fe21e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -239,8 +239,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -252,8 +252,8 @@ select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='p
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | modification | isdir 
-------+----------+------+--------------+-------
+ path | filename | size | access | modification | change | creation | isdir 
+------+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v22-0010-pg_ls_-pg_stat_file-to-show-file-type.patch (21.9K, ../../20201028193402.GA28528@telsasoft.com/11-v22-0010-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 6a3e40d83fcdf74b0db74bf45a16c92c346e5846 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v22 10/10] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 16 +++---
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/bin/pg_rewind/libpq_fetch.c              |  2 +-
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 38 ++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 7 files changed, 103 insertions(+), 63 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 809483feed..b8a0c2c3b1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25826,7 +25826,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25854,7 +25854,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25883,7 +25883,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25916,7 +25916,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25990,13 +25990,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 20b948b746..843d650ddd 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -753,5 +791,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index a9535d3dd5..d05a9e962e 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -184,7 +184,7 @@ libpqProcessFileList(void)
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
+		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
 		"FROM pg_ls_dir_recurse('.') files\n"
 		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3f62aea9c9..e87b80ce47 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6193,16 +6193,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10933,13 +10933,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10949,13 +10949,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9147', descr => 'check pages of a relation',
   proname => 'pg_relation_check_pages', procost => '10000', prorows => '20',
@@ -10967,21 +10967,21 @@
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4be27fe21e..9bfc445d99 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,38 +222,38 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
-  path  |    filename    | isdir 
---------+----------------+-------
- pg_wal | archive_status | t
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
+  path  |    filename    | type 
+--------+----------------+------
+ pg_wal | archive_status | d
 (1 row)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | access | modification | change | creation | isdir 
-------+----------+------+--------+--------------+--------+----------+-------
+ path | filename | size | access | modification | change | creation | type 
+------+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index fda95828d7..b87965a1bd 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-11-05 13:51                                             ` Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 0 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-11-05 13:51 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>

On Wed, Oct 28, 2020 at 02:34:02PM -0500, Justin Pryzby wrote:
> On Tue, Sep 08, 2020 at 02:51:26PM -0500, Justin Pryzby wrote:
> > On Sat, Jul 18, 2020 at 03:15:32PM -0500, Justin Pryzby wrote:
> > > Still waiting for feedback from a committer.
> > 
> > This patch has been waiting for input from a committer on the approach I've
> > taken with the patches since March 10, so I'm planning to set to "Ready" - at
> > least ready for senior review.
> 
> @cfbot: rebased

Rebased on e152506adef4bc503ea7b8ebb4fedc0b8eebda81

-- 
Justin

Attachments:

  [text/x-diff] v23-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20201105135157.GY22691@telsasoft.com/2-v23-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 3e98e1e782f46d3b4ae8240d8cbb608af80bc9f8 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v23 01/10] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b1dc264f6..f068260188 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25908,7 +25908,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v23-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20201105135157.GY22691@telsasoft.com/3-v23-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 9cef25af7c265e040def5cf89286ec068edca075 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v23 02/10] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f068260188..7b1dc264f6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25908,7 +25908,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d34182a7b0..9f4927220b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -406,7 +406,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v23-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.0K, ../../20201105135157.GY22691@telsasoft.com/4-v23-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 3b72b3e0941bd10e796744019adea3570fc64063 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v23 03/10] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 18 ++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  5 +++++
 2 files changed, 23 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..2e87c548eb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,24 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..f6857ad177 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,11 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v23-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20201105135157.GY22691@telsasoft.com/5-v23-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 53a04a7528c8aeb9d4522a3edc824f94536144aa Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v23 04/10] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 229 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 220 insertions(+), 91 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b1dc264f6..0a924fdcc3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25738,6 +25738,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c4b6..e7647787cf 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1503,6 +1503,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..6f003bbf67 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c01da4bf01..add3c7dc62 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10971,6 +10971,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..20bfe71822 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..68e2bc6586 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
 select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
 select pg_ls_dir('does not exist'); -- fails with missingok=false
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v23-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20201105135157.GY22691@telsasoft.com/6-v23-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From cb41776e0a61cc83b72c4bf0ac90ce18b6a1e6ed Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v23 05/10] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonbly complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0a924fdcc3..ee23bb763c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25840,16 +25840,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 6f003bbf67..ec016149b6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if missing_ok.
  * Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index add3c7dc62..cffcd03634 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10963,13 +10963,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 20bfe71822..0144f611c2 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v23-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20201105135157.GY22691@telsasoft.com/7-v23-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From b9b8c7a893862d75d09c4d24705d70876280232a Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v23 06/10] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ee23bb763c..729d201249 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25749,7 +25749,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25768,12 +25769,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25791,13 +25794,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25815,13 +25819,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ec016149b6..a07d1717f9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -680,14 +677,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -706,7 +703,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +733,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cffcd03634..344d6305e5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10947,18 +10947,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0144f611c2..bb926a2cf4 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v23-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch (8.6K, ../../20201105135157.GY22691@telsasoft.com/8-v23-0007-Add-pg_ls_dir_recurse-to-show-dir-recursively.patch)
  download | inline diff:
From 313c2c4128e70776e248afb973cca0af2125eeda Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v23 07/10] Add pg_ls_dir_recurse to show dir recursively..

..possibly there's a better place to put this, like maybe a doc-only example ?

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       | 32 ++++++++++++++++++++
 src/backend/catalog/system_views.sql         |  1 +
 src/bin/pg_rewind/libpq_source.c             | 22 +++-----------
 src/bin/pg_rewind/t/RewindTest.pm            |  7 ++++-
 src/include/catalog/pg_proc.dat              |  6 ++++
 src/test/regress/expected/misc_functions.out | 13 ++++++++
 src/test/regress/sql/misc_functions.sql      |  6 ++++
 7 files changed, 69 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 729d201249..c95dab6e1d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25760,6 +25760,38 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_recurse</primary>
+        </indexterm>
+        <function>pg_ls_dir_recurse</function> ( <parameter>dirname</parameter> <type>text</type> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>name</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
+       </para>
+       <para>
+        Recursively list each file in the specified directory, along with the
+        files' metadata.
+       </para>
+       <para>
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
+   <!--para>
+    To recursively list temporary directories in all tablespaces:
+<programlisting>
+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;
+</programlisting>
+   </para-->
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e7647787cf..148dd7a8a8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1504,6 +1504,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_recurse(text) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index c73e8bf470..46c8b7fcd5 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -205,30 +205,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_recurse('.') files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 41ed7d4b3b..cacd58651c 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,12 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool)
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_recurse(text)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 344d6305e5..5e8f16d5b1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10983,6 +10983,12 @@
   proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
   proargnames => '{dirname,filename,size,modification,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
+  proargnames => '{dirname,path,filename,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.modification, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index bb926a2cf4..302f348a6d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,19 @@ select * from pg_ls_dir_metadata('.') limit 0;
 ----------+------+--------------+-------
 (0 rows)
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+  path  |    filename    | isdir 
+--------+----------------+-------
+ pg_wal | archive_status | t
+(1 row)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_recurse('.') limit 0;
+ path | filename | size | modification | isdir 
+------+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 68e2bc6586..fda95828d7 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -76,6 +76,12 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
+select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+
+-- Check that expected columns are present
+select * from pg_ls_dir_recurse('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v23-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20201105135157.GY22691@telsasoft.com/9-v23-0008-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 4c96231e9ef1241ce71754e1bd7a837d74479efa Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v23 08/10] pg_ls_logdir to ignore error if initial/top dir is
 missing

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a07d1717f9..f7a0cb3f46 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v23-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (20.2K, ../../20201105135157.GY22691@telsasoft.com/10-v23-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 045bc8f987376c106000b39166a660b38ae69ac7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v23 09/10] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 37 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 34 +++++-----
 src/test/regress/expected/misc_functions.out | 16 ++---
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 89 insertions(+), 77 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c95dab6e1d..1b6b6130c7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25749,7 +25749,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25769,7 +25772,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25801,13 +25807,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25826,13 +25835,16 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25851,14 +25863,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25878,13 +25893,17 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index f7a0cb3f46..ca2b577a8d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -397,6 +399,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_stat_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -407,7 +431,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -447,27 +471,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	tuple_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -608,8 +614,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -648,23 +654,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5e8f16d5b1..a995780dfc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10947,48 +10947,48 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,bool}',
-  proargnames => '{dirname,path,filename,size,modification,isdir}', proargmodes => '{i,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.modification, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 302f348a6d..4be27fe21e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,8 +222,8 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -239,8 +239,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
@@ -252,8 +252,8 @@ select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='p
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | modification | isdir 
-------+----------+------+--------------+-------
+ path | filename | size | access | modification | change | creation | isdir 
+------+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v23-0010-pg_ls_-pg_stat_file-to-show-file-type.patch (21.8K, ../../20201105135157.GY22691@telsasoft.com/11-v23-0010-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 6c46ee1691333d27db2b95751a0462a087e1eb5a Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v23 10/10] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 16 +++---
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             |  2 +-
 src/include/catalog/pg_proc.dat              | 38 ++++++-------
 src/test/regress/expected/misc_functions.out | 38 ++++++-------
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  6 +-
 7 files changed, 103 insertions(+), 63 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1b6b6130c7..a9808cbac6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25817,7 +25817,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25845,7 +25845,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25874,7 +25874,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25907,7 +25907,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25981,13 +25981,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ca2b577a8d..816710f4d5 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE				(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_stat_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -753,5 +791,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 46c8b7fcd5..417be9b525 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -213,7 +213,7 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
+		"SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
 		"FROM pg_ls_dir_recurse('.') files\n"
 		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a995780dfc..90876e297b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6205,16 +6205,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10947,13 +10947,13 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10963,32 +10963,32 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 { oid => '9981', descr => 'list all files in a directory recursively',
   proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
-  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4be27fe21e..9bfc445d99 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,38 +222,38 @@ ERROR:  could not open directory "does not exist": No such file or directory
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
-  path  |    filename    | isdir 
---------+----------------+-------
- pg_wal | archive_status | t
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
+  path  |    filename    | type 
+--------+----------------+------
+ pg_wal | archive_status | d
 (1 row)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | access | modification | change | creation | isdir 
-------+----------+------+--------+--------------+--------+----------+-------
+ path | filename | size | access | modification | change | creation | type 
+------+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index fda95828d7..b87965a1bd 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
 -- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
 
 -- Check that expected columns are present
 select * from pg_ls_dir_recurse('.') limit 0;
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-11-23 21:14                                             ` Tom Lane <tgl@sss.pgh.pa.us>
  2020-11-23 23:00                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
  2020-11-29 17:21                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 3 replies; 70+ messages in thread

From: Tom Lane @ 2020-11-23 21:14 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Justin Pryzby <pryzby@telsasoft.com> writes:
>> This patch has been waiting for input from a committer on the approach I've
>> taken with the patches since March 10, so I'm planning to set to "Ready" - at
>> least ready for senior review.

I took a quick look through this.  This is just MHO, of course:

* I don't think it's okay to change the existing signatures of
pg_ls_logdir() et al.  Even if you can make an argument that it's
not too harmful to add more output columns, replacing pg_stat_file's
isdir output with something of a different name and datatype is most
surely not OK --- there is no possible way that doesn't break existing
user queries.

I think possibly a more acceptable approach is to leave these functions
alone but add documentation explaining how to get the additional info.
You could say things along the lines of "pg_ls_waldir() is the same as
pg_ls_dir_metadata('pg_wal') except for showing fewer columns."

* I'm not very much on board with implementing pg_ls_dir_recurse()
as a SQL function that depends on a WITH RECURSIVE construct.
I do not think that's okay from either performance or security
standpoints.  Surely it can't be hard to build a recursion capability
into the C code?  We could then also debate whether this ought to be
a separate function at all, instead of something you invoke via an
additional boolean flag parameter to pg_ls_dir_metadata().

* I'm fairly unimpressed with the testing approach, because it doesn't
seem like you're getting very much coverage.  It's hard to do better while
still having the totally-fixed output expected by our regular regression
test framework, but to me that just says not to test these functions in
that framework.  I'd consider ripping all of that out in favor of a
TAP test.

While I didn't read the C code in any detail, a couple of things stood
out to me:

* I noticed that you did s/stat/lstat/.  That's fine on Unix systems,
but it won't have any effect on Windows systems (cf bed90759f),
which means that we'll have to document a platform-specific behavioral
difference.  Do we want to go there?  Maybe this patch needs to wait
on somebody fixing our lack of real lstat() on Windows.  (I assume BTW
that this means the WIN32 code in get_file_type() is unreachable.)

* This bit:

+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;

doesn't seem to have thought very carefully about the interaction
of those two flags, ie it seems like LS_DIR_SKIP_HIDDEN effectively
implies LS_DIR_SKIP_DOT_DIRS.  Do we want that?

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
@ 2020-11-23 23:00                                               ` Stephen Frost <sfrost@snowman.net>
  2020-11-23 23:06                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 70+ messages in thread

From: Stephen Frost @ 2020-11-23 23:00 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Justin Pryzby <pryzby@telsasoft.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Justin Pryzby <pryzby@telsasoft.com> writes:
> >> This patch has been waiting for input from a committer on the approach I've
> >> taken with the patches since March 10, so I'm planning to set to "Ready" - at
> >> least ready for senior review.
> 
> I took a quick look through this.  This is just MHO, of course:
> 
> * I don't think it's okay to change the existing signatures of
> pg_ls_logdir() et al.  Even if you can make an argument that it's
> not too harmful to add more output columns, replacing pg_stat_file's
> isdir output with something of a different name and datatype is most
> surely not OK --- there is no possible way that doesn't break existing
> user queries.

I disagree that we need to stress over this- we pretty routinely change
the signature of various catalogs and functions and anyone using these
is already of the understanding that we are free to make such changes
between major versions.  If anything, we should be strongly discouraging
the notion of "don't break user queries" when it comes to administrative
and monitoring functions like these because, otherwise, we end up with
things like the mess that is pg_start/stop_backup() (and just contrast
that to what we did to recovery.conf when thinking about "well, do we
need to 'deprecate' or keep around the old stuff so we don't break
things for users who use these functions?" or the changes made in v10,
neither of which have produced much in the way of complaints).

Let's focus on working towards cleaner APIs and functions, accepting a
break when it makes sense to, which seems to be the case with this patch
(though I agree about using a TAP test suite and about performing the
directory recursion in C instead), and not pull forward cruft that we
then are telling ourselves we have to maintain compatibility of
indefinitely and at the expense of sensible APIs.

Thanks,

Stephen

Attachments:

  [application/pgp-signature] signature.asc (818B, ../../20201123230031.GR16415@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-11-23 23:00                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
@ 2020-11-23 23:06                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
  2020-11-24 16:53                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
  0 siblings, 1 reply; 70+ messages in thread

From: Tom Lane @ 2020-11-23 23:06 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: Justin Pryzby <pryzby@telsasoft.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Stephen Frost <sfrost@snowman.net> writes:
> * Tom Lane (tgl@sss.pgh.pa.us) wrote:
>> I took a quick look through this.  This is just MHO, of course:
>> 
>> * I don't think it's okay to change the existing signatures of
>> pg_ls_logdir() et al.

> I disagree that we need to stress over this- we pretty routinely change
> the signature of various catalogs and functions and anyone using these
> is already of the understanding that we are free to make such changes
> between major versions.

Well, like I said, just MHO.  Anybody else want to weigh in?

I'm mostly concerned about removing the isdir output of pg_stat_file().
Maybe we could compromise to the extent of keeping that, allowing it
to be partially duplicative of a file-type-code output column.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-11-23 23:00                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
  2020-11-23 23:06                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
@ 2020-11-24 16:53                                                   ` Stephen Frost <sfrost@snowman.net>
  0 siblings, 0 replies; 70+ messages in thread

From: Stephen Frost @ 2020-11-24 16:53 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Justin Pryzby <pryzby@telsasoft.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Greetings,

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > * Tom Lane (tgl@sss.pgh.pa.us) wrote:
> >> I took a quick look through this.  This is just MHO, of course:
> >> 
> >> * I don't think it's okay to change the existing signatures of
> >> pg_ls_logdir() et al.
> 
> > I disagree that we need to stress over this- we pretty routinely change
> > the signature of various catalogs and functions and anyone using these
> > is already of the understanding that we are free to make such changes
> > between major versions.
> 
> Well, like I said, just MHO.  Anybody else want to weigh in?
> 
> I'm mostly concerned about removing the isdir output of pg_stat_file().
> Maybe we could compromise to the extent of keeping that, allowing it
> to be partially duplicative of a file-type-code output column.

I don't have any particular issue with keeping isdir as a convenience
column.  I agree it'll now be a bit duplicative but that seems alright.

Thanks,

Stephen

Attachments:

  [application/pgp-signature] signature.asc (818B, ../../20201124165322.GS16415@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
@ 2020-11-29 17:21                                               ` Justin Pryzby <pryzby@telsasoft.com>
  2020-12-04 17:23                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2020-11-29 17:21 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Mon, Nov 23, 2020 at 04:14:18PM -0500, Tom Lane wrote:
> Justin Pryzby <pryzby@telsasoft.com> writes:
> >> This patch has been waiting for input from a committer on the approach I've
> >> taken with the patches since March 10, so I'm planning to set to "Ready" - at
> >> least ready for senior review.
> 
> I took a quick look through this.  This is just MHO, of course:
> 
> * I don't think it's okay to change the existing signatures of
> pg_ls_logdir() et al.  Even if you can make an argument that it's
> not too harmful to add more output columns, replacing pg_stat_file's
> isdir output with something of a different name and datatype is most
> surely not OK --- there is no possible way that doesn't break existing
> user queries.
> 
> I think possibly a more acceptable approach is to leave these functions
> alone but add documentation explaining how to get the additional info.
> You could say things along the lines of "pg_ls_waldir() is the same as
> pg_ls_dir_metadata('pg_wal') except for showing fewer columns."
> 
> * I'm not very much on board with implementing pg_ls_dir_recurse()
> as a SQL function that depends on a WITH RECURSIVE construct.
> I do not think that's okay from either performance or security
> standpoints.  Surely it can't be hard to build a recursion capability

Thanks.  WITH RECURSIVE was the "new approach" I took early this year.  Of
course we can recurse in C, now that I know (how) to use the tuplestore.
Working on that patch was how I ran into the "LIMIT 1" SRF bug.

I don't see how security is relevant though, though, since someone can run a
the WITH query directly.  The function just needs to be restricted to
superusers, same as pg_ls_dir().

Anyway, I've re-ordered commits so this the last patch, since earlier commits
don't need to depend on it.  I don't think it's even essential to provide a
recursive function (anyone could write the CTE), so long as we don't hide dirs
and show isdir or type.

I implemented it first as a separate function and then as an optional argument
to pg_ls_dir_files().  If it's implemented as an optional "mode" of an existing
function, there's the constraint that returning a "path" argument has to be
after all other arguments (the ones that are useful without recursion) or else
it messes up other functions (like pg_ls_waldir()) that also call
pg_ls_dir_files().

> doesn't seem to have thought very carefully about the interaction
> of those two flags, ie it seems like LS_DIR_SKIP_HIDDEN effectively
> implies LS_DIR_SKIP_DOT_DIRS.  Do we want that?

Yes it's implied.  Those options exist to support the pre-existing behavior.
pg_ls_dir can optionaly show dotdirs, but pg_ls_*dir skip all hidden files
(which is documented since 8b6d94cf6).  I'm happy to implement something else
if a different behavior is desirable.

-- 
Justin

Attachments:

  [text/x-diff] v24-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20201129172115.GO24052@telsasoft.com/2-v24-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From e313fdfab9b0f0538978eae8bd0d37c13d21453a Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v24 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507bc1a668..9c0ad7a334 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25904,7 +25904,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v24-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20201129172115.GO24052@telsasoft.com/3-v24-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 9e0bc43162286eadbdc1548ba240eeef0f4ca451 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v24 02/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9c0ad7a334..507bc1a668 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25904,7 +25904,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d34182a7b0..9f4927220b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -406,7 +406,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v24-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20201129172115.GO24052@telsasoft.com/4-v24-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 2c73030bbccd58beba1c16f45ccac91659715884 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v24 03/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..edbfd9abc1 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..b32a3a4b74 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v24-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20201129172115.GO24052@telsasoft.com/5-v24-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 9e822f632e047e3ed5b632e1a0321d2b88ac56e9 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v24 04/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 231 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 221 insertions(+), 92 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507bc1a668..5e267c4f8d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25757,6 +25757,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c4b6..e7647787cf 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1503,6 +1503,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..7351615f8a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -561,17 +511,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
  * Generic function to return a directory listing of files.
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e7fbda9f81..f35a684dcf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10975,6 +10975,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index edbfd9abc1..fd7c3c791f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -225,6 +225,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b32a3a4b74..64d9d70874 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -68,6 +68,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v24-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20201129172115.GO24052@telsasoft.com/6-v24-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 06e53b7907508141d00a5384292fe915585703ed Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v24 05/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5e267c4f8d..dcd0aa84d2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25836,16 +25836,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 7351615f8a..da7d4a7c11 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f35a684dcf..e2aea6f48b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10967,13 +10967,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index fd7c3c791f..7279c83d89 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v24-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20201129172115.GO24052@telsasoft.com/7-v24-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From bd3b240ea3c2aae9f9354d674e2290426d9a1e1e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v24 06/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dcd0aa84d2..191cf7c9fa 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25743,12 +25743,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25768,7 +25770,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25787,13 +25790,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25811,13 +25815,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index da7d4a7c11..d21a95aebe 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -680,14 +677,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -706,7 +703,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +733,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e2aea6f48b..bb57f3bc70 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10951,18 +10951,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7279c83d89..800d12b4b6 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v24-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20201129172115.GO24052@telsasoft.com/8-v24-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 98bdf11f229743f837419d481b001985ad3a2ebd Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v24 07/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d21a95aebe..07fce2a9bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v24-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.6K, ../../20201129172115.GO24052@telsasoft.com/9-v24-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 7ceab34939281df3b00d82c5930d0674ce3380ec Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v24 08/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 28 ++++----
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 81 insertions(+), 72 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 191cf7c9fa..e3d7df2dd7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25743,13 +25743,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25770,7 +25773,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25790,13 +25796,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25815,14 +25824,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25842,13 +25854,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 07fce2a9bc..9ff06f4158 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -397,6 +399,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -407,7 +431,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -447,27 +471,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	tuple_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -608,8 +614,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -648,23 +654,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bb57f3bc70..7fea405068 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10951,41 +10951,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 800d12b4b6..1f4106cd9c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -245,8 +245,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v24-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (21.5K, ../../20201129172115.GO24052@telsasoft.com/10-v24-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From c13fb72a36b347ac9a8f1293abeef32488eecc47 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v24 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 36 ++++++------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 101 insertions(+), 61 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e3d7df2dd7..55e9f5a8b2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25747,13 +25747,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25777,7 +25777,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25800,13 +25800,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25828,14 +25828,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25858,7 +25858,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -25868,7 +25868,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25942,13 +25942,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9ff06f4158..6e8898029f 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -753,5 +791,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fea405068..12a7391e73 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6202,16 +6202,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10951,41 +10951,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1f4106cd9c..5b87ab4844 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -221,32 +221,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 64d9d70874..0961cdc058 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -72,9 +72,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v24-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20201129172115.GO24052@telsasoft.com/11-v24-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From 00121bd9554cae0d60b768b41ee4026dd89575e6 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v24 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 6e8898029f..5c4a7f748d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -468,11 +468,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -494,7 +495,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -506,11 +507,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	tuple_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 12a7391e73..d472afcf71 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6202,16 +6202,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 5b87ab4844..e1e9ced303 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -221,8 +221,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v24-0011-Add-recursion-option-in-pg_ls_dir_files.patch (17.7K, ../../20201129172115.GO24052@telsasoft.com/12-v24-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From 480f5220229420fe031338dc38883ed761c19621 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v24 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_views.sql         |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 17 +++--
 src/test/regress/expected/misc_functions.out | 52 ++++++++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 146 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 55e9f5a8b2..6fa76d2011 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25769,7 +25769,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25777,7 +25778,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e7647787cf..6e4024f8cb 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1503,7 +1503,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5c4a7f748d..14ce7da3e4 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -40,6 +40,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -48,6 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -580,7 +583,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -590,9 +592,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -600,14 +601,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -659,10 +676,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -678,7 +705,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -703,14 +734,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 47beba277a..9ebb31615c 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 41ed7d4b3b..d911b7de52 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d472afcf71..c9880b95ac 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10977,17 +10977,24 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
+
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
   proname => 'satisfies_hash_partition', provariadic => 'any',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e1e9ced303..580c7c4922 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,17 +238,63 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.', true, true, true) limit 0;
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
+(0 rows)
+
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where path!~'[0-9]|pg_internal.init|global.tmp' order by 1;
+               path               |       filename        | type 
+----------------------------------+-----------------------+------
+ base                             | base                  | d
+ base/pgsql_tmp                   | pgsql_tmp             | d
+ global                           | global                | d
+ global/pg_control                | pg_control            | -
+ global/pg_filenode.map           | pg_filenode.map       | -
+ pg_commit_ts                     | pg_commit_ts          | d
+ pg_dynshmem                      | pg_dynshmem           | d
+ pg_hba.conf                      | pg_hba.conf           | -
+ pg_ident.conf                    | pg_ident.conf         | -
+ pg_logical                       | pg_logical            | d
+ pg_logical/mappings              | mappings              | d
+ pg_logical/replorigin_checkpoint | replorigin_checkpoint | -
+ pg_logical/snapshots             | snapshots             | d
+ pg_multixact                     | pg_multixact          | d
+ pg_multixact/members             | members               | d
+ pg_multixact/offsets             | offsets               | d
+ pg_notify                        | pg_notify             | d
+ pg_replslot                      | pg_replslot           | d
+ pg_serial                        | pg_serial             | d
+ pg_snapshots                     | pg_snapshots          | d
+ pg_stat                          | pg_stat               | d
+ pg_stat_tmp                      | pg_stat_tmp           | d
+ pg_stat_tmp/global.stat          | global.stat           | -
+ pg_subtrans                      | pg_subtrans           | d
+ pg_tblspc                        | pg_tblspc             | d
+ pg_twophase                      | pg_twophase           | d
+ PG_VERSION                       | PG_VERSION            | -
+ pg_wal                           | pg_wal                | d
+ pg_wal/archive_status            | archive_status        | d
+ pg_xact                          | pg_xact               | d
+ postgresql.auto.conf             | postgresql.auto.conf  | -
+ postgresql.conf                  | postgresql.conf       | -
+ postmaster.opts                  | postmaster.opts       | -
+ postmaster.pid                   | postmaster.pid        | -
+(34 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 0961cdc058..f4b1d3d605 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -74,11 +74,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.', true, true, true) limit 0;
+
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where path!~'[0-9]|pg_internal.init|global.tmp' order by 1;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-11-29 17:21                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-12-04 17:23                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-09 16:37                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Tom Lane @ 2020-12-04 17:23 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Justin Pryzby <pryzby@telsasoft.com> writes:
[ v24-0001-Document-historic-behavior-of-links-to-directori.patch ]

The cfbot is unhappy with one of the test cases you added:

6245@@ -259,9 +259,11 @@
6246 select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where path!~'[0-9]|pg_internal.init|global.tmp' order by 1;
6247                path               |       filename        | type 
6248 ----------------------------------+-----------------------+------
6249+ PG_VERSION                       | PG_VERSION            | -
6250  base                             | base                  | d
6251  base/pgsql_tmp                   | pgsql_tmp             | d
6252  global                           | global                | d
6253+ global/config_exec_params        | config_exec_params    | -
6254  global/pg_control                | pg_control            | -
6255  global/pg_filenode.map           | pg_filenode.map       | -
6256  pg_commit_ts                     | pg_commit_ts          | d
6257@@ -285,7 +287,6 @@
6258  pg_subtrans                      | pg_subtrans           | d
6259  pg_tblspc                        | pg_tblspc             | d
6260  pg_twophase                      | pg_twophase           | d
6261- PG_VERSION                       | PG_VERSION            | -
6262  pg_wal                           | pg_wal                | d
6263  pg_wal/archive_status            | archive_status        | d
6264  pg_xact                          | pg_xact               | d
6265@@ -293,7 +294,7 @@
6266  postgresql.conf                  | postgresql.conf       | -
6267  postmaster.opts                  | postmaster.opts       | -
6268  postmaster.pid                   | postmaster.pid        | -
6269-(34 rows)
6270+(35 rows)

This shows that (a) the test is sensitive to prevailing collation and
(b) it's not filtering out enough temporary files.  Even if those things
were fixed, though, the test would break every time we added/removed
some PGDATA substructure.  Worse, it'd also break if say somebody had
edited postgresql.conf and left an editor backup file behind, or when
running in an installation where the configuration files are someplace
else.  I think this is way too fragile to be acceptable.

Maybe it could be salvaged by reversing the sense of the WHERE condition
so that instead of trying to blacklist stuff, you whitelist just a small
number of files that should certainly be there.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-11-29 17:21                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-12-04 17:23                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
@ 2020-12-09 16:37                                                   ` Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 0 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-12-09 16:37 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Fri, Dec 04, 2020 at 12:23:23PM -0500, Tom Lane wrote:
> Justin Pryzby <pryzby@telsasoft.com> writes:
> [ v24-0001-Document-historic-behavior-of-links-to-directori.patch ]
> 
> The cfbot is unhappy with one of the test cases you added:

> Maybe it could be salvaged by reversing the sense of the WHERE condition
> so that instead of trying to blacklist stuff, you whitelist just a small
> number of files that should certainly be there.

Yes, I had noticed this one.

-- 
Justin

Attachments:

  [text/x-diff] v25-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20201209163742.GS24052@telsasoft.com/2-v25-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 00d390ac3754db140bb98c5b09bc38e3e9b0e45c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v25 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df29af6371..7cafdb9107 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25897,7 +25897,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v25-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20201209163742.GS24052@telsasoft.com/3-v25-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 9ac2f2cf76a53348c505482f8df27ab0464006d1 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v25 02/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7cafdb9107..df29af6371 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25897,7 +25897,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d34182a7b0..9f4927220b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -406,7 +406,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v25-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20201209163742.GS24052@telsasoft.com/4-v25-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From a7729e28c3361fad3adc09388a59d47bdce46eb1 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v25 03/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..edbfd9abc1 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..b32a3a4b74 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v25-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.0K, ../../20201209163742.GS24052@telsasoft.com/5-v25-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From e3adc14a91c3ae80281b2d34855d98efdc7ecd7b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v25 04/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 231 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 221 insertions(+), 92 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df29af6371..226923a134 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25750,6 +25750,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c210bc..93d4f1e4a6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1506,6 +1506,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..7351615f8a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -561,17 +511,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
  * Generic function to return a directory listing of files.
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fc2202b843..8e88c549d9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10976,6 +10976,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index edbfd9abc1..fd7c3c791f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -225,6 +225,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b32a3a4b74..64d9d70874 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -68,6 +68,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v25-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.7K, ../../20201209163742.GS24052@telsasoft.com/6-v25-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From bbbf870852824f7d582cf8fef5fe95263d9473c6 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v25 05/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  4 ++--
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 226923a134..4fb4ec9f33 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25829,16 +25829,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 7351615f8a..da7d4a7c11 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -517,7 +517,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
  * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8e88c549d9..a0148e6130 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10968,13 +10968,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index fd7c3c791f..7279c83d89 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v25-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20201209163742.GS24052@telsasoft.com/7-v25-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 8b7914717e25e6b01d6fe518d68d9e502c6e2746 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v25 06/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4fb4ec9f33..c4a673c8eb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25736,12 +25736,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25761,7 +25763,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25780,13 +25783,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25804,13 +25808,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index da7d4a7c11..d21a95aebe 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -680,14 +677,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -706,7 +703,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +733,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a0148e6130..fc6402d19b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10952,18 +10952,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7279c83d89..800d12b4b6 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v25-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20201209163742.GS24052@telsasoft.com/8-v25-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 94a0f74730991359c0782f3bde7642ad3cea21e2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v25 07/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d21a95aebe..07fce2a9bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v25-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.6K, ../../20201209163742.GS24052@telsasoft.com/9-v25-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 11b2e087fa0a3c697f6f5e87f3d65b2a4d2d8157 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v25 08/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 28 ++++----
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 81 insertions(+), 72 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c4a673c8eb..f294730605 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25736,13 +25736,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25763,7 +25766,10 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -25783,13 +25789,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -25808,14 +25817,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -25835,13 +25847,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 07fce2a9bc..9ff06f4158 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -397,6 +399,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -407,7 +431,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -447,27 +471,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	tuple_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -608,8 +614,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -648,23 +654,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fc6402d19b..3f43958423 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10952,41 +10952,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 800d12b4b6..1f4106cd9c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -245,8 +245,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v25-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (21.5K, ../../20201209163742.GS24052@telsasoft.com/10-v25-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From d605c7f32e53a4b7131145a1f65b926534afe5eb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v25 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 36 ++++++------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 101 insertions(+), 61 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f294730605..cd4375f75b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25740,13 +25740,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25770,7 +25770,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -25793,13 +25793,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25821,14 +25821,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25851,7 +25851,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -25861,7 +25861,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -25935,13 +25935,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9ff06f4158..6e8898029f 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -753,5 +791,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3f43958423..414b087756 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6203,16 +6203,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10952,41 +10952,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1f4106cd9c..5b87ab4844 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -221,32 +221,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 64d9d70874..0961cdc058 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -72,9 +72,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v25-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20201209163742.GS24052@telsasoft.com/11-v25-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From 1fa9c2d90c61830acd51433c1f3f272a32a665e8 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v25 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 6e8898029f..5c4a7f748d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -468,11 +468,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -494,7 +495,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -506,11 +507,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	tuple_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 414b087756..a75929cb92 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6203,16 +6203,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 5b87ab4844..e1e9ced303 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -221,8 +221,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v25-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20201209163742.GS24052@telsasoft.com/12-v25-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From 10f0d4215493e0f44dfa7e399ccd9978f6cf7a94 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v25 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_views.sql         |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 17 +++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 120 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index cd4375f75b..7f7d674767 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25762,7 +25762,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25770,7 +25771,8 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 93d4f1e4a6..2ce4b27e0f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1506,7 +1506,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5c4a7f748d..14ce7da3e4 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -40,6 +40,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -48,6 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -580,7 +583,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -590,9 +592,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -600,14 +601,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -659,10 +676,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -678,7 +705,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -703,14 +734,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			tuple_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 47beba277a..9ebb31615c 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 41ed7d4b3b..d911b7de52 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a75929cb92..b54be0bc63 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10978,17 +10978,24 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
+
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
   proname => 'satisfies_hash_partition', provariadic => 'any',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e1e9ced303..b14547c857 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,17 +238,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select lower(path), filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by 1;
+         lower          |    filename     | type 
+------------------------+-----------------+------
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_version             | PG_VERSION      | -
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 0961cdc058..328cf42e47 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -74,11 +74,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select lower(path), filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by 1;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
@ 2020-12-23 19:17                                               ` Justin Pryzby <pryzby@telsasoft.com>
  2020-12-23 19:27                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2 siblings, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2020-12-23 19:17 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Mon, Nov 23, 2020 at 04:14:18PM -0500, Tom Lane wrote:
> * I don't think it's okay to change the existing signatures of
> pg_ls_logdir() et al.  Even if you can make an argument that it's
> not too harmful to add more output columns, replacing pg_stat_file's
> isdir output with something of a different name and datatype is most
> surely not OK --- there is no possible way that doesn't break existing
> user queries.
> 
> I think possibly a more acceptable approach is to leave these functions
> alone but add documentation explaining how to get the additional info.
> You could say things along the lines of "pg_ls_waldir() is the same as
> pg_ls_dir_metadata('pg_wal') except for showing fewer columns."

On Mon, Nov 23, 2020 at 06:06:19PM -0500, Tom Lane wrote:
> I'm mostly concerned about removing the isdir output of pg_stat_file().
> Maybe we could compromise to the extent of keeping that, allowing it
> to be partially duplicative of a file-type-code output column.

On Tue, Nov 24, 2020 at 11:53:22AM -0500, Stephen Frost wrote:
> I don't have any particular issue with keeping isdir as a convenience
> column.  I agree it'll now be a bit duplicative but that seems alright.

Maybe we should do what Tom said, and leave pg_ls_* unchanged, but also mark
them as deprecated in favour of:
| pg_ls_dir_metadata(dir), dir={'pg_wal/archive_status', 'log', 'pg_wal', 'base/pgsql_tmp'}

However, pg_ls_tmpdir is special since it handles tablespace tmpdirs, which it
seems is not trivial to get from sql:

+SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
+FROM pg_tablespace b, pg_control_system() pcs,
+LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
+LATERAL pg_ls_dir_recurse(dir) AS a;

For context, the line of reasoning that led me to this patch series was
something like this:

0) Why can't I list shared tempfiles (dirs) using pg_ls_tmpdir() ?
1) Implement recursion for pg_ls_tmpdir();
2) Eventually realize that it's silly to implement a function to recurse into
   one particular directory when no general feature exists;
3) Implement generic facility;

> * I noticed that you did s/stat/lstat/.  That's fine on Unix systems,
> but it won't have any effect on Windows systems (cf bed90759f),
> which means that we'll have to document a platform-specific behavioral
> difference.  Do we want to go there?
>
> Maybe this patch needs to wait on somebody fixing our lack of real lstat() on Windows.

I think only the "top" patches depend on lstat (for the "type" column and
recursion, to avoid loops).  The initial patches are independently useful, and
resolve the original issue of hiding tmpdirs.  I've rebased and re-arranged the
patches to reflect this.

-- 
Justin

Attachments:

  [text/x-diff] v26-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20201223191710.GR30237@telsasoft.com/2-v26-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 1fffd1c7af05298295e96fff189caf6eb6cc1539 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v26 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2707e757ca..806a7b8a9a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26559,7 +26559,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v26-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20201223191710.GR30237@telsasoft.com/3-v26-0002-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 19c080b6163833323c53e48519f850d21d3a618d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v26 02/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 806a7b8a9a..2707e757ca 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26559,7 +26559,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d34182a7b0..9f4927220b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -406,7 +406,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v26-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20201223191710.GR30237@telsasoft.com/4-v26-0003-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From f48fe911cd4603407259b95df4e134d5c6a5ebae Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v26 03/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d3acb98d04..edbfd9abc1 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -201,6 +201,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 094e8f8296..b32a3a4b74 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -60,6 +60,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v26-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.1K, ../../20201223191710.GR30237@telsasoft.com/5-v26-0004-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 38ccc3f84e7d714e5fc8780308707daa1f6c36dd Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v26 04/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 233 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 222 insertions(+), 93 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2707e757ca..79d30a1568 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25799,6 +25799,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c210bc..93d4f1e4a6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1506,6 +1506,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..198a99391a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -561,17 +511,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 22970f46cd..17b03d1abd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11266,6 +11266,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index edbfd9abc1..fd7c3c791f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -225,6 +225,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b32a3a4b74..64d9d70874 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -68,6 +68,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v26-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.3K, ../../20201223191710.GR30237@telsasoft.com/6-v26-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 9ecdaec032a4d6281f606dc4bc2bf68e26bcf4b0 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v26 05/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 79d30a1568..c68c9d548b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26491,16 +26491,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 198a99391a..da7d4a7c11 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -706,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 17b03d1abd..d2594c2e45 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11258,13 +11258,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index fd7c3c791f..7279c83d89 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index a42714bf40..1c88e914e3 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v26-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20201223191710.GR30237@telsasoft.com/7-v26-0006-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 34b808f2740b7d4d3deaf6dc184219b4c22582a1 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v26 06/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c68c9d548b..d154302172 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25810,7 +25810,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26419,12 +26420,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26442,13 +26445,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26466,13 +26470,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index da7d4a7c11..d21a95aebe 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -680,14 +677,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -706,7 +703,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +733,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d2594c2e45..908283bd19 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11242,18 +11242,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 7279c83d89..800d12b4b6 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v26-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20201223191710.GR30237@telsasoft.com/8-v26-0007-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From a6efbcc1e84135c052d8c1b82d14324250f738df Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v26 07/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d21a95aebe..07fce2a9bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v26-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.6K, ../../20201223191710.GR30237@telsasoft.com/9-v26-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 3f513234a46dbd9f83d498c57457aa6f1439035b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v26 08/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 28 ++++----
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 81 insertions(+), 72 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d154302172..017d761399 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25810,7 +25810,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -26420,13 +26423,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26445,13 +26451,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -26470,14 +26479,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26497,13 +26509,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 07fce2a9bc..0bf148435f 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -397,6 +399,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -407,7 +431,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -447,27 +471,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -608,8 +614,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -648,23 +654,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 908283bd19..30c21a7c6d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11242,41 +11242,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 800d12b4b6..1f4106cd9c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -228,8 +228,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -245,8 +245,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index ba9a3fe29a..1e1e02b589 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v26-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (21.5K, ../../20201223191710.GR30237@telsasoft.com/10-v26-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From b1a5f7bf0434cb69cecb034e375cf981ec84b48f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v26 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 36 ++++++------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 101 insertions(+), 61 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 017d761399..46e7ff089d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25814,7 +25814,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26427,13 +26427,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26455,13 +26455,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26483,14 +26483,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26513,7 +26513,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -26523,7 +26523,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26597,13 +26597,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0bf148435f..0855f5e88e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -418,7 +456,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -753,5 +791,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 30c21a7c6d..5d80cfa6c1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6203,16 +6203,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11242,41 +11242,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1f4106cd9c..5b87ab4844 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -221,32 +221,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 64d9d70874..0961cdc058 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -72,9 +72,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v26-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20201223191710.GR30237@telsasoft.com/11-v26-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From 342fb899b6007a2c126e720cb8ee396bdb8059d5 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v26 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0855f5e88e..2ec44863b8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -468,11 +468,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -494,7 +495,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -506,11 +507,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	values_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5d80cfa6c1..ee0c5eccec 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6203,16 +6203,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 5b87ab4844..e1e9ced303 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -221,8 +221,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v26-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20201223191710.GR30237@telsasoft.com/12-v26-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From c7ed5d58606829c2c9504f0084238c8aed6db737 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v26 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_views.sql         |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 17 +++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 120 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 46e7ff089d..04b602c32f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25806,7 +25806,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25814,7 +25815,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 93d4f1e4a6..2ce4b27e0f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1506,7 +1506,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 2ec44863b8..89ee751aea 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -40,6 +40,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -48,6 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -580,7 +583,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -590,9 +592,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -600,14 +601,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -659,10 +676,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -678,7 +705,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -703,14 +734,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 47beba277a..9ebb31615c 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 41ed7d4b3b..d911b7de52 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ee0c5eccec..1344db62d2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11268,17 +11268,24 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9979', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '9980', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
+
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
   proname => 'satisfies_hash_partition', provariadic => 'any',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e1e9ced303..b14547c857 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,17 +238,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select lower(path), filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by 1;
+         lower          |    filename     | type 
+------------------------+-----------------+------
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_version             | PG_VERSION      | -
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 0961cdc058..328cf42e47 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -74,11 +74,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select lower(path), filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by 1;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2020-12-23 19:27                                                 ` Stephen Frost <sfrost@snowman.net>
  2021-03-15 12:47                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) David Steele <david@pgmasters.net>
  1 sibling, 1 reply; 70+ messages in thread

From: Stephen Frost @ 2020-12-23 19:27 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Greetings,

* Justin Pryzby (pryzby@telsasoft.com) wrote:
> On Mon, Nov 23, 2020 at 04:14:18PM -0500, Tom Lane wrote:
> > * I don't think it's okay to change the existing signatures of
> > pg_ls_logdir() et al.  Even if you can make an argument that it's
> > not too harmful to add more output columns, replacing pg_stat_file's
> > isdir output with something of a different name and datatype is most
> > surely not OK --- there is no possible way that doesn't break existing
> > user queries.
> > 
> > I think possibly a more acceptable approach is to leave these functions
> > alone but add documentation explaining how to get the additional info.
> > You could say things along the lines of "pg_ls_waldir() is the same as
> > pg_ls_dir_metadata('pg_wal') except for showing fewer columns."
> 
> On Mon, Nov 23, 2020 at 06:06:19PM -0500, Tom Lane wrote:
> > I'm mostly concerned about removing the isdir output of pg_stat_file().
> > Maybe we could compromise to the extent of keeping that, allowing it
> > to be partially duplicative of a file-type-code output column.
> 
> On Tue, Nov 24, 2020 at 11:53:22AM -0500, Stephen Frost wrote:
> > I don't have any particular issue with keeping isdir as a convenience
> > column.  I agree it'll now be a bit duplicative but that seems alright.
> 
> Maybe we should do what Tom said, and leave pg_ls_* unchanged, but also mark
> them as deprecated in favour of:
> | pg_ls_dir_metadata(dir), dir={'pg_wal/archive_status', 'log', 'pg_wal', 'base/pgsql_tmp'}

Haven't really time to review the patches here in detail right now
(maybe next month), but in general, I dislike marking things as
deprecated.  If we don't want to change them and we're happy to continue
supporting them as-is (which is what 'deprecated' really means), then we
can just do so- nothing stops us from that.  If we don't think the
current API makes sense, for whatever reason, we can just change that-
there's no need for a 'deprecation period', as we already have major
versions and support each major version for 5 years.

I haven't particularly strong feelings one way or the other regarding
these particular functions.  If you asked which way I leaned, I'd say
that I'd rather redefine the functions to make more sense and to be easy
to use for people who would like to use them.  I wouldn't object to new
functions to provide that either though.  I don't think there's all that
much code or that it's changed often enough to be a big burden to keep
both, but that's more feeling than anything based in actual research at
this point.

Thanks,

Stephen

Attachments:

  [application/pgp-signature] signature.asc (818B, ../../20201223192732.GQ27507@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-12-23 19:27                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
@ 2021-03-15 12:47                                                   ` David Steele <david@pgmasters.net>
  0 siblings, 0 replies; 70+ messages in thread

From: David Steele @ 2021-03-15 12:47 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; Justin Pryzby <pryzby@telsasoft.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On 12/23/20 2:27 PM, Stephen Frost wrote:
> * Justin Pryzby (pryzby@telsasoft.com) wrote:
>> On Mon, Nov 23, 2020 at 04:14:18PM -0500, Tom Lane wrote:
>>> * I don't think it's okay to change the existing signatures of
>>> pg_ls_logdir() et al.  Even if you can make an argument that it's
>>> not too harmful to add more output columns, replacing pg_stat_file's
>>> isdir output with something of a different name and datatype is most
>>> surely not OK --- there is no possible way that doesn't break existing
>>> user queries.
>>>
>>> I think possibly a more acceptable approach is to leave these functions
>>> alone but add documentation explaining how to get the additional info.
>>> You could say things along the lines of "pg_ls_waldir() is the same as
>>> pg_ls_dir_metadata('pg_wal') except for showing fewer columns."
>>
>> On Mon, Nov 23, 2020 at 06:06:19PM -0500, Tom Lane wrote:
>>> I'm mostly concerned about removing the isdir output of pg_stat_file().
>>> Maybe we could compromise to the extent of keeping that, allowing it
>>> to be partially duplicative of a file-type-code output column.
>>
>> On Tue, Nov 24, 2020 at 11:53:22AM -0500, Stephen Frost wrote:
>>> I don't have any particular issue with keeping isdir as a convenience
>>> column.  I agree it'll now be a bit duplicative but that seems alright.
>>
>> Maybe we should do what Tom said, and leave pg_ls_* unchanged, but also mark
>> them as deprecated in favour of:
>> | pg_ls_dir_metadata(dir), dir={'pg_wal/archive_status', 'log', 'pg_wal', 'base/pgsql_tmp'}
> 
> Haven't really time to review the patches here in detail right now
> (maybe next month), but in general, I dislike marking things as
> deprecated.  
Stephen, are you still planning to review these patches?

Regards,
-- 
-David
david@pgmasters.net





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2021-04-06 16:01                                                 ` Justin Pryzby <pryzby@telsasoft.com>
  2021-04-09 04:14                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2021-04-06 16:01 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Wed, Dec 23, 2020 at 01:17:10PM -0600, Justin Pryzby wrote:
> On Mon, Nov 23, 2020 at 04:14:18PM -0500, Tom Lane wrote:
> > * I noticed that you did s/stat/lstat/.  That's fine on Unix systems,
> > but it won't have any effect on Windows systems (cf bed90759f),
> > which means that we'll have to document a platform-specific behavioral
> > difference.  Do we want to go there?
> >
> > Maybe this patch needs to wait on somebody fixing our lack of real lstat() on Windows.
> 
> I think only the "top" patches depend on lstat (for the "type" column and
> recursion, to avoid loops).  The initial patches are independently useful, and
> resolve the original issue of hiding tmpdirs.  I've rebased and re-arranged the
> patches to reflect this.

I said that, but then failed to attach the re-arranged patches.
Now I also renumbered OIDs following best practice.

The first handful of patches address the original issue, and I think could be
"ready":

$ git log --oneline origin..pg-ls-dir-new |tac
... Document historic behavior of links to directories..
... Add tests on pg_ls_dir before changing it
... Add pg_ls_dir_metadata to list a dir with file metadata..
... pg_ls_tmpdir to show directories and "isdir" argument..
... pg_ls_*dir to show directories and "isdir" column..

These others are optional:
... pg_ls_logdir to ignore error if initial/top dir is missing..
... pg_ls_*dir to return all the metadata from pg_stat_file..

..and these maybe requires more work for lstat on windows:
... pg_stat_file and pg_ls_dir_* to use lstat()..
... pg_ls_*/pg_stat_file to show file *type*..
... Preserve pg_stat_file() isdir..
... Add recursion option in pg_ls_dir_files..

-- 
Justin

Attachments:

  [text/x-diff] v27-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20210406160131.GA6592@telsasoft.com/2-v27-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From c8a7cbd4ebbe903a8f373d637543b86b0ffa6dfd Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v27 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c6a45d9e55..977b75a531 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26990,7 +26990,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v27-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20210406160131.GA6592@telsasoft.com/3-v27-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From c571e40d0938d897500fbd6fb7a88cb52139da92 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v27 02/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e845042d38..ea0fc48dbd 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -214,6 +214,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index a398349afc..eb6ac12ab4 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,6 +69,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v27-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.1K, ../../20210406160131.GA6592@telsasoft.com/4-v27-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 8cacf3a76485bc8f3085927f886c153d39ce3f5a Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v27 03/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 233 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 222 insertions(+), 93 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 977b75a531..76ae2f2dd8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,6 +25793,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5f2541d316..fcd050b752 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1607,6 +1607,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 322152ebd9..f59f853983 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -719,5 +738,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4309fa40dd..b85bee2d1e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11517,6 +11517,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ea0fc48dbd..b4112ec298 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,6 +238,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index c133e73499..a32212be04 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..5f7ce3d09f 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index eb6ac12ab4..c169e527d9 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -77,6 +77,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v27-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.3K, ../../20210406160131.GA6592@telsasoft.com/5-v27-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 446447815ad73ab90c433455bb59da8a400a36cb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v27 04/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 76ae2f2dd8..09a5b5b48a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26922,16 +26922,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index f59f853983..a657d8d431 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b85bee2d1e..10ffab88c5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11509,13 +11509,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b4112ec298..0d3e88fccf 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 5f7ce3d09f..4183a77b23 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v27-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20210406160131.GA6592@telsasoft.com/6-v27-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From c71bb7086cf37a09b0bdea6d8d17a1230683f297 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v27 05/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 09a5b5b48a..d7226a9bf7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25804,7 +25804,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26850,12 +26851,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26873,13 +26876,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26897,13 +26901,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a657d8d431..a7d1a65f10 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -683,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -709,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -739,7 +736,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 10ffab88c5..5916add493 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11493,18 +11493,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0d3e88fccf..96b662f316 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v27-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20210406160131.GA6592@telsasoft.com/7-v27-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 932f1deb7ad1d9f427f3fab697a9bace13530198 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v27 06/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a7d1a65f10..e25010d40c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a32212be04..bb184d3fe0 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 4183a77b23..3571f14626 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v27-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.6K, ../../20210406160131.GA6592@telsasoft.com/8-v27-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 05d5eb14f0aa3f7393df39b9f1325e473e0da707 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v27 07/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 28 ++++----
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 81 insertions(+), 72 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d7226a9bf7..31776a6296 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25804,7 +25804,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -26851,13 +26854,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26876,13 +26882,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -26901,14 +26910,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26928,13 +26940,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e25010d40c..8f83ec08f6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -400,6 +402,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -410,7 +434,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -450,27 +474,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -611,8 +617,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -651,23 +657,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5916add493..491422c9f5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11493,41 +11493,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 96b662f316..ddd35b79f5 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -258,8 +258,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 3571f14626..34f874d3f1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v27-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20210406160131.GA6592@telsasoft.com/9-v27-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 0de851e57705eb77edb38aef457625f91d1b9407 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v27 08/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 31776a6296..a01971d5fe 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27034,7 +27034,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 8f83ec08f6..17e05b853e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -445,7 +445,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -635,7 +635,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v27-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (21.5K, ../../20210406160131.GA6592@telsasoft.com/10-v27-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 6dce0ebcce7ab57f73dc6797391728925b98d0ca Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v27 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 36 ++++++------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 101 insertions(+), 61 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a01971d5fe..0a7083efac 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25808,7 +25808,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26858,13 +26858,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26886,13 +26886,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26914,14 +26914,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26944,7 +26944,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -26954,7 +26954,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27028,13 +27028,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 17e05b853e..af666d658b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -756,5 +794,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 491422c9f5..0a9d1e46bd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6302,16 +6302,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11493,41 +11493,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ddd35b79f5..db3d64de3b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -234,32 +234,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 34f874d3f1..21f3e68392 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index c169e527d9..55bed7b9e0 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -81,9 +81,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v27-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20210406160131.GA6592@telsasoft.com/11-v27-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From a5948a22970f25d1856ef27205c9480a0f14f4ca Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v27 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index af666d658b..ff31e38484 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -471,11 +471,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -497,7 +498,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -509,11 +510,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	values_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0a9d1e46bd..89ce701654 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6302,16 +6302,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index db3d64de3b..349a549744 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -234,8 +234,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v27-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20210406160131.GA6592@telsasoft.com/12-v27-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From 1f83b8a1419e9ccee69897f6e9e53cf7b193412b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v27 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_views.sql         |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 17 +++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 120 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0a7083efac..34d939f77b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25800,7 +25800,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25808,7 +25809,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index fcd050b752..d915a3b342 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1607,7 +1607,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ff31e38484..ecb5b9fda9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -40,6 +40,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -48,6 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -583,7 +586,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -593,9 +595,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -603,14 +604,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -662,10 +679,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -681,7 +708,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -706,14 +737,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index ac794cf4eb..814d3de3fc 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 41ed7d4b3b..d911b7de52 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 89ce701654..d6ca02e2d0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11519,17 +11519,24 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
+{ oid => '9981', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
+
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
   proname => 'satisfies_hash_partition', provariadic => 'any',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 349a549744..3cb27fc997 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -251,17 +251,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+          path          |    filename     | type 
+------------------------+-----------------+------
+ PG_VERSION             | PG_VERSION      | -
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 55bed7b9e0..d8c9194719 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -83,11 +83,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2021-04-09 04:14                                                   ` Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 0 replies; 70+ messages in thread

From: Justin Pryzby @ 2021-04-09 04:14 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

Breaking with tradition, the previous patch included one too *few* changes, and
failed to resolve the OID collisions.

Attachments:

  [text/x-diff] v28-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20210409041432.GQ6592@telsasoft.com/2-v28-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From d3d33b25e8571f5a23333a3124e85164321f19ca Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v28 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0606b6a9aa..aa0dcde886 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27018,7 +27018,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v28-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20210409041432.GQ6592@telsasoft.com/3-v28-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From c6300e4cc9fd6826530ac5a5c49eaac7f02c49c0 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v28 02/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e845042d38..ea0fc48dbd 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -214,6 +214,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index a398349afc..eb6ac12ab4 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,6 +69,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v28-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.1K, ../../20210409041432.GQ6592@telsasoft.com/4-v28-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 5d352f9fee18f44a03f5c373b4aec71f88933402 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v28 03/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_views.sql         |   1 +
 src/backend/utils/adt/genfile.c              | 233 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 222 insertions(+), 93 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index aa0dcde886..507a6d73f8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25821,6 +25821,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 451db2ee0a..dd84b0f11a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1632,6 +1632,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 322152ebd9..f59f853983 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -719,5 +738,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f4957653ae..550fbf734a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11534,6 +11534,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ea0fc48dbd..b4112ec298 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,6 +238,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index c133e73499..a32212be04 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..5f7ce3d09f 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index eb6ac12ab4..c169e527d9 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -77,6 +77,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v28-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.3K, ../../20210409041432.GQ6592@telsasoft.com/5-v28-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 005b44ebc221094bce1151b5abe0ca852e152aa4 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v28 04/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507a6d73f8..940c653807 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26950,16 +26950,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index f59f853983..a657d8d431 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 550fbf734a..441cbb2cde 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11526,13 +11526,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b4112ec298..0d3e88fccf 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 5f7ce3d09f..4183a77b23 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v28-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20210409041432.GQ6592@telsasoft.com/6-v28-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From a5d78f9d766d4f115a669f1207d30c40859a6dd0 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v28 05/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 940c653807..0da1373c0f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25832,7 +25832,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26878,12 +26879,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26901,13 +26904,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26925,13 +26929,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a657d8d431..a7d1a65f10 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -683,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -709,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -739,7 +736,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 441cbb2cde..b3c00d9077 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11510,18 +11510,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0d3e88fccf..96b662f316 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v28-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20210409041432.GQ6592@telsasoft.com/7-v28-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From eb380c78a9e43959c0b2c07460563599a72bcc23 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v28 06/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a7d1a65f10..e25010d40c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a32212be04..bb184d3fe0 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 4183a77b23..3571f14626 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v28-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.6K, ../../20210409041432.GQ6592@telsasoft.com/8-v28-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From ace97fa677ce1bf319c4349a1711cf438206f3f5 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v28 07/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 28 ++++----
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 81 insertions(+), 72 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0da1373c0f..727018826b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25832,7 +25832,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -26879,13 +26882,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26904,13 +26910,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -26929,14 +26938,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26956,13 +26968,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e25010d40c..8f83ec08f6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -400,6 +402,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -410,7 +434,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -450,27 +474,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -611,8 +617,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -651,23 +657,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b3c00d9077..08eea899d3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11510,41 +11510,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 96b662f316..ddd35b79f5 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -258,8 +258,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 3571f14626..34f874d3f1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v28-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20210409041432.GQ6592@telsasoft.com/9-v28-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From a33861868fbb25c0cec7e55f4888f423f1b73f90 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v28 08/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 727018826b..402c10e30b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27062,7 +27062,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 8f83ec08f6..17e05b853e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -445,7 +445,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -635,7 +635,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v28-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (21.5K, ../../20210409041432.GQ6592@telsasoft.com/10-v28-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 24917a414b3b70000a4df33f1f2fd404cce3f66e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v28 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 36 ++++++------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 101 insertions(+), 61 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 402c10e30b..f81323771d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25836,7 +25836,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26886,13 +26886,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26914,13 +26914,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26942,14 +26942,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26972,7 +26972,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -26982,7 +26982,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27056,13 +27056,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 17e05b853e..af666d658b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -756,5 +794,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 08eea899d3..2fc1793b5a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6319,16 +6319,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11510,41 +11510,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ddd35b79f5..db3d64de3b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -234,32 +234,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 34f874d3f1..21f3e68392 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index c169e527d9..55bed7b9e0 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -81,9 +81,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v28-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20210409041432.GQ6592@telsasoft.com/11-v28-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From 30ddce907b2b495f78b935e9677cfb0e300305ef Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v28 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index af666d658b..ff31e38484 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -471,11 +471,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -497,7 +498,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -509,11 +510,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	values_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2fc1793b5a..a768176032 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6319,16 +6319,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index db3d64de3b..349a549744 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -234,8 +234,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v28-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20210409041432.GQ6592@telsasoft.com/12-v28-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From 381b5f646f4b1412bd746c3fcd04e4f644d552bb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v28 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_views.sql         |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 16 ++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 119 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f81323771d..985244b644 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25828,7 +25828,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25836,7 +25837,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index dd84b0f11a..572db7f035 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1632,7 +1632,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ff31e38484..ecb5b9fda9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -40,6 +40,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -48,6 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -583,7 +586,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -593,9 +595,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -603,14 +604,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -662,10 +679,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -681,7 +708,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -706,14 +737,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index ac794cf4eb..814d3de3fc 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 41ed7d4b3b..d911b7de52 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -160,7 +160,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a768176032..b6f2439380 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11536,16 +11536,22 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '8449', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 349a549744..3cb27fc997 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -251,17 +251,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+          path          |    filename     | type 
+------------------------+-----------------+------
+ PG_VERSION             | PG_VERSION      | -
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 55bed7b9e0..d8c9194719 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -83,11 +83,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2021-07-02 19:16                                                   ` Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2021-07-02 19:16 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>

On Tue, Apr 06, 2021 at 11:01:31AM -0500, Justin Pryzby wrote:
> On Wed, Dec 23, 2020 at 01:17:10PM -0600, Justin Pryzby wrote:
> > On Mon, Nov 23, 2020 at 04:14:18PM -0500, Tom Lane wrote:
> > > * I noticed that you did s/stat/lstat/.  That's fine on Unix systems,
> > > but it won't have any effect on Windows systems (cf bed90759f),
> > > which means that we'll have to document a platform-specific behavioral
> > > difference.  Do we want to go there?
> > >
> > > Maybe this patch needs to wait on somebody fixing our lack of real lstat() on Windows.
> > 
> > I think only the "top" patches depend on lstat (for the "type" column and
> > recursion, to avoid loops).  The initial patches are independently useful, and
> > resolve the original issue of hiding tmpdirs.  I've rebased and re-arranged the
> > patches to reflect this.
> 
> I said that, but then failed to attach the re-arranged patches.
> Now I also renumbered OIDs following best practice.
> 
> The first handful of patches address the original issue, and I think could be
> "ready":
> 
> $ git log --oneline origin..pg-ls-dir-new |tac
> ... Document historic behavior of links to directories..
> ... Add tests on pg_ls_dir before changing it
> ... Add pg_ls_dir_metadata to list a dir with file metadata..
> ... pg_ls_tmpdir to show directories and "isdir" argument..
> ... pg_ls_*dir to show directories and "isdir" column..
> 
> These others are optional:
> ... pg_ls_logdir to ignore error if initial/top dir is missing..
> ... pg_ls_*dir to return all the metadata from pg_stat_file..
> 
> ..and these maybe requires more work for lstat on windows:
> ... pg_stat_file and pg_ls_dir_* to use lstat()..
> ... pg_ls_*/pg_stat_file to show file *type*..
> ... Preserve pg_stat_file() isdir..
> ... Add recursion option in pg_ls_dir_files..

@cfbot: rebased

Attachments:

  [text/x-diff] v30-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20210702191637.GV21248@telsasoft.com/2-v30-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 4b34d394af0253dc3be2e47cb2e8ccd286eea0a3 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v30 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6388385edc..7a830f0684 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27004,7 +27004,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v30-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20210702191637.GV21248@telsasoft.com/3-v30-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From effa738721a008a5d0c65defecfb7b36ab8f33b3 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v30 02/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e845042d38..ea0fc48dbd 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -214,6 +214,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index a398349afc..eb6ac12ab4 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,6 +69,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v30-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.1K, ../../20210702191637.GV21248@telsasoft.com/4-v30-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 11144bab1e1af1830cdfed0e9994a4d0036e4ba2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v30 03/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 233 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 222 insertions(+), 93 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7a830f0684..dd9b1afde4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25848,6 +25848,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index a416e94d37..649a78605a 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -698,6 +698,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c436d9318b..8efb798b2f 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -451,6 +466,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -478,79 +498,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -563,17 +513,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -582,6 +534,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -596,8 +574,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -616,20 +606,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -644,13 +641,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -664,14 +682,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -689,7 +707,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -718,5 +737,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fde251fa4f..b405aa341c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11547,6 +11547,18 @@
   proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
   proargnames => '{tablespace,name,size,modification}',
   prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ea0fc48dbd..b4112ec298 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,6 +238,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index c133e73499..a32212be04 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..5f7ce3d09f 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index eb6ac12ab4..c169e527d9 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -77,6 +77,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

  [text/x-diff] v30-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.3K, ../../20210702191637.GV21248@telsasoft.com/5-v30-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 98725d8245a27ea7506f5975954f2dc31f1019bb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v30 04/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dd9b1afde4..2adc8f7d83 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26936,16 +26936,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 8efb798b2f..243e86a2a3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -708,7 +708,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b405aa341c..0a4caf8aed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11539,13 +11539,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b4112ec298..0d3e88fccf 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 5f7ce3d09f..4183a77b23 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v30-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (9.1K, ../../20210702191637.GV21248@telsasoft.com/6-v30-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From cd57a7dbf3e779de8d262e32a1903cb9b138da6c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v30 05/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 15 ++++----
 src/include/catalog/pg_proc.dat              | 12 +++----
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2adc8f7d83..73fc6b9553 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25859,7 +25859,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26864,12 +26865,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26887,13 +26890,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26911,13 +26915,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 243e86a2a3..12bb70c442 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -46,11 +46,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -682,14 +679,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -708,7 +705,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -738,7 +735,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0a4caf8aed..bc068415b4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11523,18 +11523,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0d3e88fccf..96b662f316 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v30-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20210702191637.GV21248@telsasoft.com/7-v30-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From b03e5d6881b4b0ff8ab3b8ca9e51528fa976471e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v30 06/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 12bb70c442..f3437d306a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -679,7 +679,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a32212be04..bb184d3fe0 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 4183a77b23..3571f14626 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v30-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (17.6K, ../../20210702191637.GV21248@telsasoft.com/8-v30-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From bdae8ed05f2488e85a2075b3edf25191961a626c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v30 07/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 28 ++++----
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 81 insertions(+), 72 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 73fc6b9553..4c8faf7628 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25859,7 +25859,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -26865,13 +26868,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26890,13 +26896,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -26915,14 +26924,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -26942,13 +26954,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index f3437d306a..934e1d65e1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -399,6 +401,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -409,7 +433,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -449,27 +473,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -610,8 +616,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -650,23 +656,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bc068415b4..282656ef82 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11523,41 +11523,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 96b662f316..ddd35b79f5 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -241,8 +241,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -258,8 +258,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 3571f14626..34f874d3f1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v30-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20210702191637.GV21248@telsasoft.com/9-v30-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From 614a321ac62fc50d53077e1639cf63f1ae5a7a59 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v30 08/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c8faf7628..df2b345829 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27048,7 +27048,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 934e1d65e1..935a81287a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -444,7 +444,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -634,7 +634,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v30-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (21.5K, ../../20210702191637.GV21248@telsasoft.com/10-v30-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 83e79c94aa197e77b01fdf30daa7c6d1e67849bb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v30 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 36 ++++++------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 101 insertions(+), 61 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df2b345829..56189c26c3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25863,7 +25863,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -26872,13 +26872,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26900,13 +26900,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26928,14 +26928,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -26958,7 +26958,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -26968,7 +26968,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27042,13 +27042,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 935a81287a..10bcb0b049 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -401,6 +402,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -420,7 +458,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -470,7 +508,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -539,10 +577,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -740,7 +778,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -755,5 +793,5 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 282656ef82..176501a87a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6303,16 +6303,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11523,41 +11523,41 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ddd35b79f5..db3d64de3b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -234,32 +234,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 34f874d3f1..21f3e68392 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index c169e527d9..55bed7b9e0 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -81,9 +81,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v30-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20210702191637.GV21248@telsasoft.com/11-v30-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From 38fc446b2472464d0d36a629fdb4f52b183a188c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v30 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 10bcb0b049..0f42714d90 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -470,11 +470,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -496,7 +497,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -508,11 +509,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	values_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 176501a87a..dfe27d2e3e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6303,16 +6303,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index db3d64de3b..349a549744 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -234,8 +234,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v30-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20210702191637.GV21248@telsasoft.com/12-v30-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From cfccac95e4834cfc74801a5032190b540f9000da Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v30 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_functions.sql     |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 16 ++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 119 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 56189c26c3..6178992f2f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25855,7 +25855,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25863,7 +25864,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 649a78605a..d96bc22e55 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -698,7 +698,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 --
 -- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0f42714d90..300ea9f933 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -40,6 +40,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -48,6 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -582,7 +585,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -592,9 +594,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -602,14 +603,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -661,10 +678,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -680,7 +707,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -705,14 +736,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 8e0783fcef..e6e893eda5 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 938c661afc..8f7bb10ee3 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -163,7 +163,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index dfe27d2e3e..3ca4937e6c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11549,16 +11549,22 @@
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '8449', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 349a549744..3cb27fc997 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -251,17 +251,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+          path          |    filename     | type 
+------------------------+-----------------+------
+ PG_VERSION             | PG_VERSION      | -
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test adding a support function to a subject function
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 55bed7b9e0..d8c9194719 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -83,11 +83,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+
 --
 -- Test adding a support function to a subject function
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2021-11-22 19:17                                                     ` Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Bossart, Nathan @ 2021-11-22 19:17 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers; Thomas Munro <thomas.munro@gmail.com>

In an attempt to get this patch set off the ground again, I took a
look at the first 5 patches.

0001: This one is a very small documentation update for pg_stat_file
to point out that isdir will be true for symbolic links to
directories.  Given this is true, I think the patch looks good.

0002: This patch adds some very basic testing for pg_ls_dir().  The
only comment that I have for this one is that I might also check
whether '..' is included in the results of the include_dot_dirs tests.
The docs specifically note that include_dot_dirs indicates whether
both '.' and '..' are included, so IMO we might as well verify that.

0003: This one didn't apply cleanly until I used 'git apply -3', so it
likely needs a rebase.  This patch introduces the pg_ls_dir_metadata()
function, which appears to just be pg_ls_dir() with some additional
columns for the size and modification time.  My initial reaction to
this one is that we should just add those columns to pg_ls_dir() to
match all the other pg_ls_* functions (and not bother attempting to
maintain historic behavior for things like hidden and special files).
I believe there is some existing discussion on this point upthread, so
perhaps there is a good reason to make a new function.  In any case, I
like the idea of having pg_ls_dir() use pg_ls_dir_files() internally
like the rest of the pg_ls_* functions.

0004: This one changes pg_ls_tmpdir to show directories as well.  I
think this is a reasonable change.  On it's own, the patch looks
alright, although it might look different if my suggestions for 0003
were followed.

0005: This one adjusts the rest of the pg_ls_* functions to show
directories.  Again, I think this is a reasonable change.  As noted in
0003, I think it'd be alright just to have all the pg_ls_* functions
show special and hidden files as well.  It's simple enough already to
filter our files that start with '.' if necessary, and I'm not sure
there's any strong reason for leaving out special files.  If special
files are included, perhaps isdir should be changed to indicate the
file type instead of just whether it is a directory.  (Reading ahead,
it looks like this is what 0009 might do.)

I haven't looked at the following patches too much, but I'm getting
the idea that they might address a lot of the feedback above and that
the first bunch of patches are more like staging patches that add the
abilities without changing the behavior.  I wonder if just going
straight to the end goal behavior might simplify the patch set a bit.
I can't say I feel too strongly about this, but I figure I'd at least
share my thoughts.

Nathan



^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
@ 2021-11-24 00:04                                                       ` Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2021-11-24 00:04 UTC (permalink / raw)
  To: Bossart, Nathan <bossartn@amazon.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Fabien COELHO <coelho@cri.ensmp.fr>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Mon, Nov 22, 2021 at 07:17:01PM +0000, Bossart, Nathan wrote:
> In an attempt to get this patch set off the ground again, I took a
> look at the first 5 patches.

> I haven't looked at the following patches too much, but I'm getting
> the idea that they might address a lot of the feedback above and that
> the first bunch of patches are more like staging patches that add the
> abilities without changing the behavior.  I wonder if just going
> straight to the end goal behavior might simplify the patch set a bit.
> I can't say I feel too strongly about this, but I figure I'd at least
> share my thoughts.

Thanks for looking.

The patches are separate since the early patches are the most necessary, least
disputable parts, to allow the possibility of (say) chaging pg_ls_tmpdir() without
changing other functions, since pg_ls_tmpdir was was original motivation behind
this whole thread.

In a recent thread, Bharath Rupireddy added pg_ls functions for the logical
dirs, but expressed a preference not to add the metadata columns.  I still
think that at least "isdir" should be added to all the "ls" functions, since
it's easy to SELECT the columns you want, and a bit of a pain to write the
corresponding LATERAL query: 'dir' AS dir, pg_ls_dir(dir) AS ls,
pg_stat_file(ls) AS st.  I think it would be strange if pg_ls_tmpdir() were to
return a different set of columns than the other functions, even though admins
or extensions might have created dirs or other files in those directories.

Tom pointed out that we don't have a working lstat() for windows, so then it
seems like we're not yet ready to show file "types" (we'd show the type of the
link target, which is sometimes what's wanted, but not usually what "ls" would
show), nor ready to implement recurse.

As before:

On Tue, Apr 06, 2021 at 11:01:31AM -0500, Justin Pryzby wrote:
> The first handful of patches address the original issue, and I think could be
> "ready":
> 
> $ git log --oneline origin..pg-ls-dir-new |tac
> ... Document historic behavior of links to directories..
> ... Add tests on pg_ls_dir before changing it
> ... Add pg_ls_dir_metadata to list a dir with file metadata..
> ... pg_ls_tmpdir to show directories and "isdir" argument..
> ... pg_ls_*dir to show directories and "isdir" column..
> 
> These others are optional:
> ... pg_ls_logdir to ignore error if initial/top dir is missing..
> ... pg_ls_*dir to return all the metadata from pg_stat_file..
> 
> ..and these maybe requires more work for lstat on windows:
> ... pg_stat_file and pg_ls_dir_* to use lstat()..
> ... pg_ls_*/pg_stat_file to show file *type*..
> ... Preserve pg_stat_file() isdir..
> ... Add recursion option in pg_ls_dir_files..

rebased on 1922d7c6e1a74178bd2f1d5aa5a6ab921b3fcd34

-- 
Justin

Attachments:

  [text/x-diff] v31-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20211124000444.GX17618@telsasoft.com/2-v31-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 6bdb81194999cfe6b0ba4d850e4f31d022655a5b Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v31 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 74d3087a72..d36479d86d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27410,7 +27410,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v31-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20211124000444.GX17618@telsasoft.com/3-v31-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From d6068e34df8dd9ca8db1959010f1b41933732775 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v31 02/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1013d17f87..830de507e7 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 7ab9b2a150..422a1369ae 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -91,6 +91,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.0

  [text/x-diff] v31-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.8K, ../../20211124000444.GX17618@telsasoft.com/4-v31-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From a82b2137b493b2e74630b433fa2411153e7b8087 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v31 03/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 239 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/input/tablespace.source     |   5 +
 src/test/regress/output/tablespace.source    |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 8 files changed, 225 insertions(+), 96 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d36479d86d..e0099e77df 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25860,6 +25860,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index f6789025a5..a45bac89ff 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -698,6 +698,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 027ed86400..0728547ec2 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -719,7 +738,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
 
 /*
@@ -728,7 +775,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
 }
 
 /*
@@ -737,7 +784,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
 }
 
 /*
@@ -762,5 +809,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, false);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e934361dc3..39fbaacbc9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11645,6 +11645,18 @@
   proargmodes => '{i,o,o,o}',
   proargnames => '{slot_name,name,size,modification}',
   prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 830de507e7..b3a9d11b5c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -267,6 +267,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index cb9774ecc8..e69fa17004 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index e7629d470e..c2f6d64c5c 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 422a1369ae..ca946d08bd 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -99,6 +99,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.0

  [text/x-diff] v31-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.3K, ../../20211124000444.GX17618@telsasoft.com/5-v31-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 93442dfa0c333cc143b9eaeef0346d04e6897cbf Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v31 04/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/output/tablespace.source    |  4 ++--
 5 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e0099e77df..9ddd23f90e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27342,16 +27342,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0728547ec2..d6be8cef6e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 39fbaacbc9..3ffdcaf3a7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11615,13 +11615,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b3a9d11b5c..a2bec47d76 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -270,8 +270,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index c2f6d64c5c..c146a4c129 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,8 +17,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v31-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (11.7K, ../../20211124000444.GX17618@telsasoft.com/6-v31-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From f87a11adfa95475ae9da2b487b2ebbc21e065e36 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v31 05/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir, ...

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 21 +++++-------
 src/include/catalog/pg_proc.dat              | 26 +++++++-------
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 45 insertions(+), 42 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9ddd23f90e..77547aa7cd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25871,7 +25871,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27270,12 +27271,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27293,13 +27296,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27317,13 +27321,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d6be8cef6e..eed71892bd 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -47,11 +47,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -683,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -709,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -739,7 +736,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -775,7 +772,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_COMMON);
 }
 
 /*
@@ -784,7 +781,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_COMMON);
 }
 
 /*
@@ -809,5 +806,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_COMMON);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3ffdcaf3a7..b442ca1130 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11599,18 +11599,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -11627,23 +11627,23 @@
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{slot_name,name,size,modification}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{slot_name,name,size,modification,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index a2bec47d76..25492860bd 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v31-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20211124000444.GX17618@telsasoft.com/7-v31-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 642fd42279e4d34395b67b221024cd0227af2019 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v31 06/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index eed71892bd..c381a170ad 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index e69fa17004..da624020b2 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index c146a4c129..3689bfac6d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v31-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (19.5K, ../../20211124000444.GX17618@telsasoft.com/8-v31-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 55d010bdf1688538eed7426680aa1a5ea89203c7 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v31 07/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 42 ++++++------
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/output/tablespace.source    |  8 +--
 5 files changed, 88 insertions(+), 79 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 77547aa7cd..20e7f10e83 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25871,7 +25871,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -27271,13 +27274,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27296,13 +27302,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -27321,14 +27330,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27348,13 +27360,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c381a170ad..3c022cffd5 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -400,6 +402,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -410,7 +434,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -450,27 +474,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -611,8 +617,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -651,23 +657,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b442ca1130..b1944b32fb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11599,63 +11599,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargmodes => '{i,o,o,o,o}',
-  proargnames => '{slot_name,name,size,modification,isdir}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 25492860bd..dc4470fe3e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -270,8 +270,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -287,8 +287,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 3689bfac6d..1fc54528c7 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v31-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20211124000444.GX17618@telsasoft.com/9-v31-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From e05fd5cffed20cd4cd9aed5f23d9c15680e282f2 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v31 08/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20e7f10e83..04d2d2c830 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27454,7 +27454,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3c022cffd5..e907f39924 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -445,7 +445,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -635,7 +635,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v31-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (23.6K, ../../20211124000444.GX17618@telsasoft.com/10-v31-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 0534275772b7abbdb0a1a44541d51a156717dccb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v31 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 48 ++++++++--------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/output/tablespace.source    |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 107 insertions(+), 67 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 04d2d2c830..ca329869cb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25875,7 +25875,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27278,13 +27278,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27306,13 +27306,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27334,14 +27334,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27364,7 +27364,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -27374,7 +27374,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27448,13 +27448,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e907f39924..bef20178ea 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,11 +37,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -50,7 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -756,7 +794,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b1944b32fb..61b39094d0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11599,63 +11599,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index dc4470fe3e..8b26b5811e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -263,32 +263,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1fc54528c7..5c97acd1e1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index ca946d08bd..a582841453 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -103,9 +103,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v31-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20211124000444.GX17618@telsasoft.com/11-v31-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From 7a87145fcc20420254654d5f41fff9fe3fcc7e09 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v31 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bef20178ea..7171357f44 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -471,11 +471,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -497,7 +498,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -509,11 +510,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	values_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61b39094d0..4508f69f48 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 8b26b5811e..1a11661d9f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -263,8 +263,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v31-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20211124000444.GX17618@telsasoft.com/12-v31-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From b42fcf35e92394e8307c1946f17ecc9bb708434c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v31 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_functions.sql     |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 16 ++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 119 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ca329869cb..60039cf92d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25867,7 +25867,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25875,7 +25876,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index a45bac89ff..d7e67a416b 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -698,7 +698,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 7171357f44..8eb7cb896e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -41,6 +41,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -49,6 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -583,7 +586,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -593,9 +595,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -603,14 +604,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -662,10 +679,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -681,7 +708,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -706,14 +737,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 8e0783fcef..e6e893eda5 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 5546ce456c..90c38425b5 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -163,7 +163,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4508f69f48..9120cb4b8d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11647,16 +11647,22 @@
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '8449', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1a11661d9f..fc63206bb6 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -280,17 +280,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+          path          |    filename     | type 
+------------------------+-----------------+------
+ PG_VERSION             | PG_VERSION      | -
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index a582841453..54e79bf176 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -105,11 +105,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2021-12-23 13:14                                                         ` Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Fabien COELHO @ 2021-12-23 13:14 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>


Hello Justin,

It seems that the v31 patch does not apply anymore:

   postgresql> git apply ~/v31-0001-Document-historic-behavior-of-links-to-directori.patch
   error: patch failed: doc/src/sgml/func.sgml:27410
   error: doc/src/sgml/func.sgml: patch does not apply

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2021-12-23 17:36                                                           ` Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2021-12-23 17:36 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Thu, Dec 23, 2021 at 09:14:18AM -0400, Fabien COELHO wrote:
> It seems that the v31 patch does not apply anymore:
> 
>   postgresql> git apply ~/v31-0001-Document-historic-behavior-of-links-to-directori.patch
>   error: patch failed: doc/src/sgml/func.sgml:27410
>   error: doc/src/sgml/func.sgml: patch does not apply

Thanks for continuing to follow this patch ;)

I fixed a conflict with output/tablespace from d1029bb5a et seq.
I'm not sure why you got a conflict with 0001, though.

I think the 2nd half of the patches are still waiting for fixes to lstat() on
windows.

You complained before that there were too many patches, and I can see how it
might be a pain depending on your workflow.  But the division is deliberate.
Dealing with patchsets is easy for me: I use "mutt" and for each patch
attachment, I type "|git am" (or |git am -3).

Attachments:

  [text/x-diff] v32-0001-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20211223173631.GE17618@telsasoft.com/2-v32-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From b8ed86daad1bf9fbb647f7c007130bb0878a2a94 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v32 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e58efce5865..b5c1befe627 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27495,7 +27495,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.0

  [text/x-diff] v32-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch (2.3K, ../../20211223173631.GE17618@telsasoft.com/3-v32-0002-Add-tests-on-pg_ls_dir-before-changing-it.patch)
  download | inline diff:
From 88b324a6877ce916589872f5067bd5810e60608e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v32 02/11] Add tests on pg_ls_dir before changing it

---
 src/test/regress/expected/misc_functions.out | 24 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      |  8 +++++++
 2 files changed, 32 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1013d17f87d..830de507e7c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -243,6 +243,30 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 7ab9b2a1509..422a1369aeb 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -91,6 +91,14 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.0

  [text/x-diff] v32-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.8K, ../../20211223173631.GE17618@telsasoft.com/4-v32-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 6b7b4a3a7f4fe3edcb5a67f467b10543fa47c343 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v32 03/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 239 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/expected/tablespace.out     |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 src/test/regress/sql/tablespace.sql          |   5 +
 8 files changed, 225 insertions(+), 96 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b5c1befe627..7948e6ec06c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25945,6 +25945,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 3a4fa9091b1..5eb6cc8572c 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -702,6 +702,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 027ed864001..0728547ec2b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+#endif
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -719,7 +738,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
 
 /*
@@ -728,7 +775,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
 }
 
 /*
@@ -737,7 +784,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
 }
 
 /*
@@ -762,5 +809,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, false);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4d992dc2241..c99e1633bbf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11645,6 +11645,18 @@
   proargmodes => '{i,o,o,o}',
   proargnames => '{slot_name,name,size,modification}',
   prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 830de507e7c..b3a9d11b5c0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -267,6 +267,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 864f4b6e208..21f7f19b67f 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -16,6 +16,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 422a1369aeb..ca946d08bde 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -99,6 +99,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 92076db9a13..291ca029c14 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -16,6 +16,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v32-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.3K, ../../20211223173631.GE17618@telsasoft.com/5-v32-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From efff69d7721d34a522cbb54f29f6bed511a0ca75 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v32 04/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  4 ++--
 src/test/regress/expected/tablespace.out     |  4 ++--
 5 files changed, 18 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7948e6ec06c..7589ef7ac8a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27427,16 +27427,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0728547ec2b..d6be8cef6e3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -709,7 +709,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c99e1633bbf..fa293eca393 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11615,13 +11615,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b3a9d11b5c0..a2bec47d76c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -270,8 +270,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 21f7f19b67f..7d8398b0ca1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -20,8 +20,8 @@ CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v32-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (11.7K, ../../20211223173631.GE17618@telsasoft.com/6-v32-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 1dd76679c8084bd6dd4f216d825e628dd48ed71c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v32 05/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir, ...

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 21 +++++-------
 src/include/catalog/pg_proc.dat              | 26 +++++++-------
 src/test/regress/expected/misc_functions.out |  4 +--
 4 files changed, 45 insertions(+), 42 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7589ef7ac8a..99243fb1b1e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25956,7 +25956,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27355,12 +27356,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27378,13 +27381,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27402,13 +27406,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d6be8cef6e3..eed71892bd1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -47,11 +47,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -683,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -709,7 +706,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -739,7 +736,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -775,7 +772,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_COMMON);
 }
 
 /*
@@ -784,7 +781,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_COMMON);
 }
 
 /*
@@ -809,5 +806,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_COMMON);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fa293eca393..1522ef041a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11599,18 +11599,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -11627,23 +11627,23 @@
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{slot_name,name,size,modification}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{slot_name,name,size,modification,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index a2bec47d76c..25492860bd8 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
-- 
2.17.0

  [text/x-diff] v32-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20211223173631.GE17618@telsasoft.com/7-v32-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From a6d374953c73d04215f06a26c7568935a8d4e97d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v32 06/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index eed71892bd1..c381a170ad6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 7d8398b0ca1..549117256fc 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -24,6 +24,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 291ca029c14..cc5f2f9d0dc 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -21,6 +21,10 @@ CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0

  [text/x-diff] v32-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (19.5K, ../../20211223173631.GE17618@telsasoft.com/8-v32-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 4ff1344fbf7ddefb0509ca9077a2d6d7758f2890 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v32 07/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 +++++++---
 src/backend/utils/adt/genfile.c              | 71 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 42 ++++++------
 src/test/regress/expected/misc_functions.out | 12 ++--
 src/test/regress/expected/tablespace.out     |  8 +--
 5 files changed, 88 insertions(+), 79 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 99243fb1b1e..745faa1841f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25956,7 +25956,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -27356,13 +27359,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27381,13 +27387,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -27406,14 +27415,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27433,13 +27445,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c381a170ad6..3c022cffd5a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -400,6 +402,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -410,7 +434,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -450,27 +474,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -611,8 +617,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -651,23 +657,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-				values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-#endif
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1522ef041a2..f8ad1827cbd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11599,63 +11599,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargmodes => '{i,o,o,o,o}',
-  proargnames => '{slot_name,name,size,modification,isdir}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 25492860bd8..dc4470fe3ee 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -270,8 +270,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -287,8 +287,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 549117256fc..db77360abf6 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -20,15 +20,15 @@ CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.0

  [text/x-diff] v32-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20211223173631.GE17618@telsasoft.com/9-v32-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From ca0bddd74bd475cfc9542f210517e540d6a3a921 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v32 08/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 745faa1841f..3650e3d71f2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27539,7 +27539,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3c022cffd5a..e907f399241 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -445,7 +445,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -635,7 +635,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.0

  [text/x-diff] v32-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (23.6K, ../../20211223173631.GE17618@telsasoft.com/10-v32-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 3fd691719784cbd7d528cebacc3e0ba49769bbae Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v32 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 48 ++++++++--------
 src/test/regress/expected/misc_functions.out | 30 +++++-----
 src/test/regress/expected/tablespace.out     |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 107 insertions(+), 67 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3650e3d71f2..5c517ed6347 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25960,7 +25960,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27363,13 +27363,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27391,13 +27391,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27419,14 +27419,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27449,7 +27449,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -27459,7 +27459,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27533,13 +27533,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e907f399241..bef20178ea6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,11 +37,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -50,7 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -756,7 +794,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f8ad1827cbd..346c4c577c0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11599,63 +11599,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index dc4470fe3ee..8b26b5811e4 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -263,32 +263,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index db77360abf6..67d3f5eed98 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -20,15 +20,15 @@ CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index ca946d08bde..a5828414531 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -103,9 +103,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.0

  [text/x-diff] v32-0010-Preserve-pg_stat_file-isdir.patch (4.5K, ../../20211223173631.GE17618@telsasoft.com/11-v32-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From 3ba13d90a51786bad56b0aee7fd86c329f656670 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v32 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bef20178ea6..7171357f442 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -471,11 +471,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -497,7 +498,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -509,11 +510,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	values_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 346c4c577c0..998f5164170 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 8b26b5811e4..1a11661d9fe 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -263,8 +263,8 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.0

  [text/x-diff] v32-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20211223173631.GE17618@telsasoft.com/12-v32-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From 7c075484c444d4b10ea0d5121130544d7e0e5b2c Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v32 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_functions.sql     |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 16 ++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 119 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5c517ed6347..4cb8c0456fe 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25952,7 +25952,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25960,7 +25961,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 5eb6cc8572c..69dbfa38942 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -702,7 +702,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 7171357f442..8eb7cb896e4 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -41,6 +41,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -49,6 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -583,7 +586,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -593,9 +595,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -603,14 +604,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -662,10 +679,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -681,7 +708,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -706,14 +737,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 8e0783fcef3..e6e893eda52 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 5546ce456cd..90c38425b5b 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -163,7 +163,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 998f5164170..02bd2d22598 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11647,16 +11647,22 @@
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '8449', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1a11661d9fe..fc63206bb6f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -280,17 +280,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+          path          |    filename     | type 
+------------------------+-----------------+------
+ PG_VERSION             | PG_VERSION      | -
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index a5828414531..54e79bf176d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -105,11 +105,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.0

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-01-02 12:07                                                             ` Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-02 13:55                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 2 replies; 70+ messages in thread

From: Fabien COELHO @ 2022-01-02 12:07 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>


Hello Justin,

Happy new year!

> I think the 2nd half of the patches are still waiting for fixes to lstat() on
> windows.

Not your problem?

Here is my review about v32:

All patches apply cleanly.

# part 01

One liner doc improvement to tell that creation time is only available on windows.
It is indeed not available on Linux.

OK.

# part 02

Add tests for various options on pg_ls_dir, and for pg_stat_file, which were not
exercised before.  "make check" is ok.

OK.

# part 03

This patch adds a new pg_ls_dir_metadata.  Internally, this is an extension of
pg_ls_dir_files function which is used by other pg_ls functions.  Doc ok.

About the code:

ISTM that the "if (1) { if (2) continue; } else if(3) { if (4) continue; }" structure"
may be simplified to "if (1 && 2) continue; if (3 && 4) continue;", at least if
IS_DIR and IS_REG are incompatible? Otherwise, at least "else if (3 & 4) continue"?

The ifdef WIN32 (which probably detects windows 64 bits…) overwrites values[3]. ISTM
it could be reordered so that there is no overwrite, and simpler single assignements.

   #ifndef WIN32
     v = ...;
   #else
     v = ... ? ... : ...;
   #endif

New tests are added which check that the result columns are configured as required,
including error cases.

"make check" is ok.

OK.

# part 04

Add a new "isdir" column to "pg_ls_tmpdir" output.  This is a small behavioral
change.  I'm ok with it, however I'm unsure why we would not jump directly to
the "type" char column done later in the patch series.  ISTM all such functions
should be extended the same way for better homogeneity? That would also impact
"waldir", "archive_status", "logical_*", "replslot" variants. "make check" ok.

OK.

# part 05

This patch applies my previous advice:-) ISTM that parts 4 and 5 should be one
single patch. The test changes show that only waldir has a test. Would it be
possible to add minimal tests to other variants as well?  "make check" ok.

I'd consider add such tests with part 02.

OK.

# part 06

This part extends and adds a test for pg_ls_logdir. ISTM that it should
be merged with the previous patches.  "make check" is ok.

OK.

# part 07

This part extends pg_stat_file with more date informations.

ISTM that the documentation should be clear about windows vs unix/cygwin specific
data provided (change/creation).

The code adds a new value_from_stat function to avoid code duplication.
Fine with me.

All pg_ls_*dir functions are impacted. Fine with me.

"make check" is ok.

OK.

# part 08

This part substitutes lstat to stat. Fine with me.  "make check" is ok.
I guess that lstat does something under windows even if the concept of
link is somehow different there. Maybe the doc should say so somewhere?

OK.

# part 09

This part switches the added "isdir" to a "type" column.  "make check" is ok.
This is a definite improvement.

OK.

# part 10

This part adds a redundant "isdir" column. I do not see the point.
"make check" is ok.

NOT OK.

# part 11

This part adds a recurse option. Why not. However, the true value does not
seem to be tested? "make check" is ok.

My opinion is unclear.

Overall, ignoring part 10, this makes a definite improvement to postgres ls
capabilities. I do not seen any reason why not to add those.

-- 
Fabien.

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2022-01-02 13:55                                                               ` Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-18 07:00                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Julien Rouhaud <rjuju123@gmail.com>
  1 sibling, 1 reply; 70+ messages in thread

From: Fabien COELHO @ 2022-01-02 13:55 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>


> Here is my review about v32:

I forgot to tell that doc generation for the cumulated changes also works.

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-02 13:55                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2022-01-18 07:00                                                                 ` Julien Rouhaud <rjuju123@gmail.com>
  0 siblings, 0 replies; 70+ messages in thread

From: Julien Rouhaud @ 2022-01-18 07:00 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Justin Pryzby <pryzby@telsasoft.com>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

Hi,

On Sun, Jan 02, 2022 at 02:55:04PM +0100, Fabien COELHO wrote:
> 
> > Here is my review about v32:
> 
> I forgot to tell that doc generation for the cumulated changes also works.

Unfortunately the patchset doesn't apply anymore:

http://cfbot.cputube.org/patch_36_2377.log
=== Applying patches on top of PostgreSQL commit ID 4483b2cf29bfe8091b721756928ccbe31c5c8e14 ===
=== applying patch ./v32-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch
[...]
patching file src/test/regress/expected/misc_functions.out
Hunk #1 succeeded at 274 (offset 7 lines).
patching file src/test/regress/expected/tablespace.out
Hunk #1 FAILED at 16.
1 out of 1 hunk FAILED -- saving rejects to file src/test/regress/expected/tablespace.out.rej

Justin, could you send a rebased version?





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
@ 2022-01-25 19:27                                                               ` Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-06-24 04:35                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2022-01-25 19:27 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Sun, Jan 02, 2022 at 01:07:29PM +0100, Fabien COELHO wrote:
> One liner doc improvement to tell that creation time is only available on windows.
> It is indeed not available on Linux.

The change is about the "isflag" flag, not creation time.

         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).

> # part 03
> ISTM that the "if (1) { if (2) continue; } else if(3) { if (4) continue; }" structure"
> may be simplified to "if (1 && 2) continue; if (3 && 4) continue;", at least if
> IS_DIR and IS_REG are incompatible?

No, what you suggested is not the same;

We talked about this before:
https://www.postgresql.org/message-id/20200315212729.GC26184@telsasoft.com

> Otherwise, at least "else if (3 & 4) continue"?

I could write the *final* "else if" like that, but then it would be different
from the previous case.  Which would be confusing and prone to mistakes.

If I wrote it like this, I think it'd just provoke suggestions from someone
else to change it differently:

        /* Skip dirs or special files? */
        if (S_ISDIR(attrib.st_mode) && !(flags & LS_DIR_SKIP_DIRS))
                continue;
        if (!S_ISDIR(attrib.st_mode) && !S_ISREG(attrib.st_mode) && !(flags & LS_DIR_SKIP_SPECIAL)
                continue;

...
<< Why don't you use "else if" instead of "if (a){} if (!a && b){}" >>

I'm going to leave it up to a committer.

> The ifdef WIN32 (which probably detects windows 64 bits…) overwrites values[3]. ISTM
> it could be reordered so that there is no overwrite, and simpler single assignements.
> 
>   #ifndef WIN32
>     v = ...;
>   #else
>     v = ... ? ... : ...;
>   #endif

I changed this but without using nested conditionals.

> Add a new "isdir" column to "pg_ls_tmpdir" output.  This is a small behavioral
> change.  I'm ok with it, however I'm unsure why we would not jump directly to
> the "type" char column done later in the patch series.

Because that depends on lstat().

> ISTM all such functions
> should be extended the same way for better homogeneity? That would also impact
> "waldir", "archive_status", "logical_*", "replslot" variants. "make check" ok.

I agree that makes sense, however others have expressed the opposite opinion.
https://www.postgresql.org/message-id/CALj2ACWtrt5EkHrY4WAZ4Cv42SidXAwpeQJU021bxaKpjmbGfA%40mail.gma...

The original motive for the patch was that pg_ls_tmpdir doesn't show shared
filesets.  This fixes that essential problem without immediately dragging
everything else along.  I think it's more likely that a committer would merge
them both.  But I don't know, and it's easy to combine patches if desired.

> This patch applies my previous advice:-) ISTM that parts 4 and 5 should be one
> single patch. The test changes show that only waldir has a test. Would it be
> possible to add minimal tests to other variants as well?  "make check" ok.

I have added tests, although some are duplicative.

> This part extends and adds a test for pg_ls_logdir. ISTM that it should
> be merged with the previous patches.  "make check" is ok.

It's seperate to allow writing a separate commit message since it does
something unrelated to the other patches.  What other patch would it would be
merged with ?
| v32-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch 

> ISTM that the documentation should be clear about windows vs unix/cygwin specific
> data provided (change/creation).

I preferred to refer to pg_stat_file rather than repeat it for all 7 functions
currently in v15, (and future functions added for new, toplevel dirs).

> # part 11
> 
> This part adds a recurse option. Why not. However, the true value does not
> seem to be tested? "make check" is ok.

WDYM the true value?  It's tested like:

+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+          path          |    filename     | type 
+------------------------+-----------------+------
+ PG_VERSION             | PG_VERSION      | -
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
...

-- 
Justin

Attachments:

  [text/x-diff] v33-0001-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20220125192755.GK23027@telsasoft.com/2-v33-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 366edbf62b85a5471f5c3ebeb28a45f2692f9815 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v33 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0ee6974f1c6..d32b414e04f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27563,7 +27563,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.1

  [text/x-diff] v33-0002-Add-tests-before-changing-pg_ls_.patch (3.4K, ../../20220125192755.GK23027@telsasoft.com/3-v33-0002-Add-tests-before-changing-pg_ls_.patch)
  download | inline diff:
From bf6f6aeab76ee8080e658c387cebec96f0a08efd Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v33 02/11] Add tests before changing pg_ls_*

---
 src/test/regress/expected/misc_functions.out | 59 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      | 15 +++++
 2 files changed, 74 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e2c77d0ac84..b7264af3ecb 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -250,6 +250,65 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalmapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalsnapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_replslotdir('') limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_tmpdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_waldir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 1159f6b5855..169f66416c3 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -94,6 +94,21 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+select * from pg_ls_logdir() limit 0;
+select * from pg_ls_logicalmapdir() limit 0;
+select * from pg_ls_logicalsnapdir() limit 0;
+select * from pg_ls_replslotdir('') limit 0;
+select * from pg_ls_tmpdir() limit 0;
+select * from pg_ls_waldir() limit 0;
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.1

  [text/x-diff] v33-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (19.7K, ../../20220125192755.GK23027@telsasoft.com/4-v33-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 64eb0ff38d8016b1ad3e2a4491d82fd95d72c754 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v33 03/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 240 +++++++++++--------
 src/include/catalog/pg_proc.dat              |  12 +
 src/test/regress/expected/misc_functions.out |  24 ++
 src/test/regress/expected/tablespace.out     |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 src/test/regress/sql/tablespace.sql          |   5 +
 8 files changed, 226 insertions(+), 96 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d32b414e04f..14b8fba3123 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25958,6 +25958,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index fd1421788e6..2ffc2f9d1a6 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -702,6 +702,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 542bbacaa24..3921723fda6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	bool		randomAccess;
-	TupleDesc	tupdesc;
-	Tuplestorestate *tupstore;
-	DIR		   *dirdesc;
-	struct dirent *de;
-	MemoryContext oldcontext;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	/* check to see if caller supports us returning a tuplestore */
-	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsinfo->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
-	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
-	tupdesc = CreateTemplateTupleDesc(1);
-	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
-	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
-	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-	rsinfo->returnMode = SFRM_Materialize;
-	rsinfo->setResult = tupstore;
-	rsinfo->setDesc = tupdesc;
-
-	MemoryContextSwitchTo(oldcontext);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	bool		randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	DIR		   *dirdesc;
 	struct dirent *de;
 	MemoryContext oldcontext;
+	TypeFuncClass	tuptype ;
+
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
 
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
+	tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+	if (flags & LS_DIR_METADATA)
+	{
+		if (tuptype != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+	}
+	else
+	{
+		/* pg_ls_dir returns a simple scalar */
+		if (tuptype != TYPEFUNC_SCALAR)
+			elog(ERROR, "return type must be a scalar type");
+		tupdesc = CreateTemplateTupleDesc(1);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+	}
 
 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -645,13 +642,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+				else
+#endif
+					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +684,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -690,7 +709,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -719,7 +739,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
 
 /*
@@ -728,7 +776,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
 }
 
 /*
@@ -737,7 +785,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
 }
 
 /*
@@ -762,5 +810,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, false);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0859dc81cac..b58987b713c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11661,6 +11661,18 @@
   proargmodes => '{i,o,o,o}',
   proargnames => '{slot_name,name,size,modification}',
   prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b7264af3ecb..a627839bc28 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -309,6 +309,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 2dfbcfdebe1..c6b9074ab02 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -24,6 +24,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 169f66416c3..bfa8e309e9a 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -109,6 +109,17 @@ select * from pg_ls_tmpdir() limit 0;
 select * from pg_ls_waldir() limit 0;
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 896f05cea32..3aadac8b611 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -23,6 +23,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v33-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.6K, ../../20220125192755.GK23027@telsasoft.com/5-v33-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From a0829a53b2e8dcf0cf92bedfdee1629bbda92c74 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v33 04/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  8 ++++----
 src/test/regress/expected/tablespace.out     |  4 ++--
 5 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 14b8fba3123..e0b0a5ab121 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27495,16 +27495,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3921723fda6..4829b34f0f6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -710,7 +710,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b58987b713c..95746fc0dcf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11631,13 +11631,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index a627839bc28..0305f038369 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -295,8 +295,8 @@ select * from pg_ls_replslotdir('') limit 0;
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
@@ -312,8 +312,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c6b9074ab02..20a7245d5e1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,8 +28,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

  [text/x-diff] v33-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (13.2K, ../../20220125192755.GK23027@telsasoft.com/6-v33-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From bd8e3587b61b1d88220faa7f510b4281c57ef2f9 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v33 05/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir, ...

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 21 +++++-------
 src/include/catalog/pg_proc.dat              | 26 +++++++-------
 src/test/regress/expected/misc_functions.out | 28 +++++++--------
 4 files changed, 57 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e0b0a5ab121..53909e68ba3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25969,7 +25969,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27350,12 +27351,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27373,13 +27376,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27470,13 +27474,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 4829b34f0f6..e24c43e3a9b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -47,11 +47,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -684,14 +681,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -710,7 +707,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -740,7 +737,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -776,7 +773,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_COMMON);
 }
 
 /*
@@ -785,7 +782,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_COMMON);
 }
 
 /*
@@ -810,5 +807,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_COMMON);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 95746fc0dcf..c5bbae2408b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11615,18 +11615,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -11643,23 +11643,23 @@
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{slot_name,name,size,modification}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{slot_name,name,size,modification,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0305f038369..91b6dba70b9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -206,8 +206,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -270,28 +270,28 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
@@ -300,8 +300,8 @@ select * from pg_ls_tmpdir() limit 0;
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
-- 
2.17.1

  [text/x-diff] v33-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20220125192755.GK23027@telsasoft.com/7-v33-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From d7f7f3b64320f9c872472f63cc0d8cd549296261 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v33 06/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e24c43e3a9b..e49a15aa279 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -681,7 +681,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 20a7245d5e1..50a3a13fbea 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -32,6 +32,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 3aadac8b611..034c903b41c 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -28,6 +28,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v33-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (21.6K, ../../20220125192755.GK23027@telsasoft.com/8-v33-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From f01934840737321d769ebab8367bcfa964bb1164 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v33 07/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 ++++++---
 src/backend/utils/adt/genfile.c              | 72 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 42 ++++++------
 src/test/regress/expected/misc_functions.out | 40 +++++------
 src/test/regress/expected/tablespace.out     |  8 +--
 5 files changed, 102 insertions(+), 94 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 53909e68ba3..a9106f13eae 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25969,7 +25969,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -27351,13 +27354,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27376,13 +27382,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -27474,14 +27483,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27501,13 +27513,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e49a15aa279..847d031af60 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -400,6 +402,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -410,7 +434,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -450,27 +474,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -611,8 +617,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -651,24 +657,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-				else
-#endif
-					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c5bbae2408b..ee7793467d0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11615,63 +11615,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargmodes => '{i,o,o,o,o}',
-  proargnames => '{slot_name,name,size,modification,isdir}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 91b6dba70b9..0db07209cc9 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -206,8 +206,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -270,38 +270,38 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
@@ -312,8 +312,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -329,8 +329,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 50a3a13fbea..b01ca30c7ab 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,15 +28,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

  [text/x-diff] v33-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch (2.3K, ../../20220125192755.GK23027@telsasoft.com/9-v33-0008-pg_stat_file-and-pg_ls_dir_-to-use-lstat.patch)
  download | inline diff:
From effabd80f66f80fef8092e6b8513268c571cb02a Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 30 Mar 2020 18:59:16 -0500
Subject: [PATCH v33 08/11] pg_stat_file and pg_ls_dir_* to use lstat()..

pg_ls_dir_* will now skip (no longer show) symbolic links, same as other
non-regular file types, as we advertize we do since 8b6d94cf6.  That seems to
be the intented behavior, since irregular file types are 1) less portable; and,
2) we don't currently show a file's type except for "bool is_dir".

pg_stat_file will now 1) show metadata of links themselves, rather than their
target; and, 2) specifically, show links to directories with "is_dir=false";
and, 3) not error on broken symlinks.
---
 doc/src/sgml/func.sgml          | 2 +-
 src/backend/utils/adt/genfile.c | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a9106f13eae..e497f9a5d23 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27607,7 +27607,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory (or a symbolic link to a directory).
+        indicating if it is a directory.
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 847d031af60..cc4fc4924ca 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -445,7 +445,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 
 	filename = convert_and_check_filename(filename_t);
 
-	if (stat(filename, &fst) < 0)
+	if (lstat(filename, &fst) < 0)
 	{
 		if (missing_ok && errno == ENOENT)
 			PG_RETURN_NULL();
@@ -635,7 +635,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		/* Get the file info */
 		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
-		if (stat(path, &attrib) < 0)
+		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
 			if (errno == ENOENT)
-- 
2.17.1

  [text/x-diff] v33-0009-pg_ls_-pg_stat_file-to-show-file-type.patch (25.8K, ../../20220125192755.GK23027@telsasoft.com/10-v33-0009-pg_ls_-pg_stat_file-to-show-file-type.patch)
  download | inline diff:
From 868776a7a5022321e42239b8f3306c19fcb69e81 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 31 Mar 2020 14:40:34 -0500
Subject: [PATCH v33 09/11] pg_ls_*/pg_stat_file to show file *type*..

..not just "isdir"

Also show special file types, now that their type is shown.
---
 doc/src/sgml/func.sgml                       | 26 +++++----
 src/backend/utils/adt/genfile.c              | 58 ++++++++++++++++----
 src/include/catalog/pg_proc.dat              | 48 ++++++++--------
 src/test/regress/expected/misc_functions.out | 58 ++++++++++----------
 src/test/regress/expected/tablespace.out     |  8 +--
 src/test/regress/sql/misc_functions.sql      |  4 +-
 6 files changed, 121 insertions(+), 81 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e497f9a5d23..f4bfa239278 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25973,7 +25973,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27358,13 +27358,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's log directory,
         return the file's name, along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27386,13 +27386,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
         file's name along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special files types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27487,14 +27487,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
         (<filename>pg_wal/archive_status</filename>), list the file's name
         along with the metadata columns returned by
         <function>pg_stat_file</function>.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27517,7 +27517,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
@@ -27527,7 +27527,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
-        Filenames beginning with a dot and special file types are excluded.
+        Filenames beginning with a dot are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27601,13 +27601,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>isdir</parameter> <type>boolean</type> )
+        <parameter>type</parameter> <type>char</type> )
        </para>
        <para>
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
-        platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        platforms only), file creation time stamp (Windows only), and a
+        character representing the file's type: regular (-), directory (d), link
+        or junction (l), character device (c), block device (b), fifo (p),
+        socket (s) or other/unknown (?).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index cc4fc4924ca..8401743efad 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,11 +37,12 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
-#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
 #define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
 #define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
@@ -50,7 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
 /* Shortcut for common behavior */
-#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+	if (S_ISREG(mode))
+		return '-';
+
+	if (S_ISDIR(mode))
+		return 'd';
+#ifndef WIN32
+	if (S_ISLNK(mode))
+		return 'l';
+#else
+	if (pgwin32_is_junction(path))
+		return 'l';
+#endif
+
+#ifdef S_ISCHR
+	if (S_ISCHR(mode))
+		return 'c';
+#endif
+#ifdef S_ISBLK
+	if (S_ISBLK(mode))
+		return 'b';
+#endif
+#ifdef S_ISFIFO
+	if (S_ISFIFO(mode))
+		return 'p';
+#endif
+#ifdef S_ISSOCK
+	if (S_ISSOCK(mode))
+		return 's';
+#endif
+
+	return '?';
+}
+
 /*
  * Populate values and nulls from fst and path.
  * Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
 	nulls[3] = true;
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
 #endif
-	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+	values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
 }
 
 /*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
-					   "isdir", BOOLOID, -1, 0);
+					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
-	/* isdir depends on metadata */
-	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
-	/* Unreasonable to show isdir and skip dirs */
-	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+	/* type depends on metadata */
+	Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show type and skip dirs XXX */
+	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
 	if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ pg_ls_dir_metadata(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
@@ -756,7 +794,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
 
 	return pg_ls_dir_files(fcinfo, dirname,
-			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+			LS_DIR_METADATA | LS_DIR_TYPE);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ee7793467d0..8f4c87c724d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11615,63 +11615,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
-  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
   proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0db07209cc9..6a6cec31a2e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -206,8 +206,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -270,67 +270,67 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir 
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type 
+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir 
-----------+-------
- .        | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type 
+----------+------
+ .        | d
 (1 row)
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir 
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type 
+----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir 
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type 
+----------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index b01ca30c7ab..68a58dd2f91 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,15 +28,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir 
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type 
+------+------+--------+--------------+--------+----------+------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index bfa8e309e9a..54543c06efe 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -113,9 +113,9 @@ select * from pg_stat_file('.') limit 0;
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
 
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
-- 
2.17.1

  [text/x-diff] v33-0010-Preserve-pg_stat_file-isdir.patch (4.3K, ../../20220125192755.GK23027@telsasoft.com/11-v33-0010-Preserve-pg_stat_file-isdir.patch)
  download | inline diff:
From af6874cd5d631c441b6ef23b544217315fd6bad5 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sat, 28 Nov 2020 22:29:31 -0600
Subject: [PATCH v33 10/11] Preserve pg_stat_file() isdir..

..per Tom's suggestion
---
 src/backend/utils/adt/genfile.c              | 15 ++++++++++++---
 src/include/catalog/pg_proc.dat              | 12 ++++++------
 src/test/regress/expected/misc_functions.out |  4 ++--
 3 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 8401743efad..9f5c2bed151 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -471,11 +471,12 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	text	   *filename_t = PG_GETARG_TEXT_PP(0);
 	char	   *filename;
 	struct stat fst;
-	Datum		values[6];
-	bool		nulls[6];
+	Datum		values[7];
+	bool		nulls[7];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
+	char		type;
 
 	/* check the optional argument */
 	if (PG_NARGS() == 2)
@@ -497,7 +498,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	 * This record type had better match the output parameters declared for me
 	 * in pg_proc.h.
 	 */
-	tupdesc = CreateTemplateTupleDesc(6);
+	tupdesc = CreateTemplateTupleDesc(7);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 1,
 					   "size", INT8OID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 2,
@@ -509,11 +510,19 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	TupleDescInitEntry(tupdesc, (AttrNumber) 5,
 					   "creation", TIMESTAMPTZOID, -1, 0);
 	TupleDescInitEntry(tupdesc, (AttrNumber) 6,
+					   "isdir", BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7,
 					   "type", CHAROID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
 	memset(nulls, false, sizeof(nulls));
 	values_from_stat(&fst, filename, values, nulls);
+
+	/* For pg_stat_file, keep isdir column for backward compatibility */
+	type = DatumGetChar(values[5]);
+	values[5] = BoolGetDatum(type == 'd'); /* isdir */
+	values[6] = CharGetDatum(type); /* file type */
+
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 8f4c87c724d..b81fbd04bc4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
 { oid => '2623', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,o,o,o,o,o,o}',
-  proargnames => '{filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file_1arg' },
 { oid => '3307', descr => 'get information about file',
   proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
   proargtypes => 'text bool',
-  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
-  proargmodes => '{i,i,o,o,o,o,o,o}',
-  proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool,char}',
+  proargmodes => '{i,i,o,o,o,o,o,o,o}',
+  proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir,type}',
   prosrc => 'pg_stat_file' },
 { oid => '2624', descr => 'read text from a file',
   proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 6a6cec31a2e..1695bf4a3a7 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -305,8 +305,8 @@ select * from pg_ls_waldir() limit 0;
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | type 
-------+--------+--------------+--------+----------+------
+ size | access | modification | change | creation | isdir | type 
+------+--------+--------------+--------+----------+-------+------
 (0 rows)
 
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
-- 
2.17.1

  [text/x-diff] v33-0011-Add-recursion-option-in-pg_ls_dir_files.patch (16.3K, ../../20220125192755.GK23027@telsasoft.com/12-v33-0011-Add-recursion-option-in-pg_ls_dir_files.patch)
  download | inline diff:
From a6ff8484d2d928115aac128aa4e5a6e8a089b5ee Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:52:14 -0500
Subject: [PATCH v33 11/11] Add recursion option in pg_ls_dir_files..

Need catversion bumped ?
---
 doc/src/sgml/func.sgml                       |  6 +-
 src/backend/catalog/system_functions.sql     |  2 +-
 src/backend/utils/adt/genfile.c              | 78 ++++++++++++++++----
 src/bin/pg_rewind/libpq_source.c             | 22 ++----
 src/bin/pg_rewind/t/RewindTest.pm            |  5 +-
 src/include/catalog/pg_proc.dat              | 16 ++--
 src/test/regress/expected/misc_functions.out | 26 ++++++-
 src/test/regress/sql/misc_functions.sql      |  8 +-
 8 files changed, 119 insertions(+), 44 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f4bfa239278..e26c5af37a7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25965,7 +25965,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         </indexterm>
         <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
         <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
-        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <parameter>include_dot_dirs</parameter> <type>boolean</type>,
+        <parameter>recurse</parameter> <type>boolean</type> </optional> )
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
@@ -25973,7 +25974,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
         <parameter>change</parameter> <type>timestamp with time zone</type>,
         <parameter>creation</parameter> <type>timestamp with time zone</type>,
-        <parameter>type</parameter> <type>char</type> )
+        <parameter>type</parameter> <type>char</type>,
+        <parameter>path</parameter> <type>text</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 2ffc2f9d1a6..37c1cf805bd 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -702,7 +702,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
-REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f5c2bed151..0509284eb28 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -41,6 +41,8 @@ static char get_file_type(mode_t mode, const char *path);
 static void values_from_stat(struct stat *fst, const char *path, Datum *values,
 		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags);
 
 #define	LS_DIR_TYPE					(1<<0) /* Show column: type */
 #define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
@@ -49,6 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything begining with . */
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+#define	LS_DIR_RECURSE				(1<<7) /* Recurse into subdirs */
 
 /* Shortcut for common behavior */
 #define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
@@ -583,7 +586,6 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
 	DIR		   *dirdesc;
-	struct dirent *de;
 	MemoryContext oldcontext;
 	TypeFuncClass	tuptype ;
 
@@ -593,9 +595,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 	Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
 
 	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
+	if (PG_NARGS() > 1 &&
+		!PG_ARGISNULL(1))
 		{
 			if (PG_GETARG_BOOL(1))
 				flags |= LS_DIR_MISSING_OK;
@@ -603,14 +604,30 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				flags &= ~LS_DIR_MISSING_OK;
 		}
 
-		if (!PG_ARGISNULL(2))
+	if (PG_NARGS() > 2 &&
+		!PG_ARGISNULL(2))
 		{
 			if (PG_GETARG_BOOL(2))
 				flags &= ~LS_DIR_SKIP_DOT_DIRS;
 			else
 				flags |= LS_DIR_SKIP_DOT_DIRS;
 		}
-	}
+
+	if (PG_NARGS() > 3 &&
+		!PG_ARGISNULL(3))
+		{
+			if (PG_GETARG_BOOL(3))
+				flags |= LS_DIR_RECURSE;
+			else
+				flags &= ~LS_DIR_RECURSE;
+		}
+
+	if ((flags & LS_DIR_RECURSE) != 0 &&
+			(flags & LS_DIR_SKIP_DOT_DIRS) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_RECURSION), // ??
+				 errmsg("recursion requires skipping dot dirs")));
+
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -662,10 +679,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
-	while ((de = ReadDir(dirdesc, dir)) != NULL)
+	pg_ls_dir_files_internal(dir, dirdesc, tupstore, tupdesc, flags);
+	FreeDir(dirdesc);
+	return (Datum) 0;
+}
+
+void pg_ls_dir_files_internal(const char *dirname, DIR *dirdesc,
+		Tuplestorestate *tupstore, TupleDesc tupdesc, int flags)
+{
+	struct dirent *de;
+
+	while ((de = ReadDir(dirdesc, dirname)) != NULL)
 	{
-		Datum		values[7];
-		bool		nulls[7];
+		Datum		values[8];
+		bool		nulls[8];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -681,7 +708,11 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 			continue;
 
 		/* Get the file info */
-		snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+		if (strcmp(dirname, ".") != 0)
+			snprintf(path, sizeof(path), "%s/%s", dirname, de->d_name);
+		else
+			snprintf(path, sizeof(path), "%s", de->d_name);
+
 		if (lstat(path, &attrib) < 0)
 		{
 			/* Ignore concurrently-deleted files, else complain */
@@ -706,14 +737,33 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
-		if (flags & LS_DIR_METADATA)
+		if ((flags & (LS_DIR_RECURSE|LS_DIR_METADATA)) != 0)
+		{
 			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
+			/*
+			 * path is only really useful for recursion, but this function
+			 * can't return different fields when recursing
+			 * XXX: return dirname (which is nice since it's the original,
+			 * unprocessed input to this recursion) or path (which is nice
+			 * since it's a "cooked" value without leading/duplicate slashes)
+			 */
+			values[7] = CStringGetTextDatum(path);
+		}
+
 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
-	}
 
-	FreeDir(dirdesc);
-	return (Datum) 0;
+		/* Recurse? */
+		if ((flags & LS_DIR_RECURSE) != 0 &&
+			S_ISDIR(attrib.st_mode))
+		{
+			DIR *newdir = AllocateDir(path);
+			/* Failure handled by ReadDir */
+			pg_ls_dir_files_internal(path, newdir, tupstore, tupdesc, flags);
+			Assert(newdir != NULL);
+			FreeDir(newdir);
+		}
+	}
 }
 
 /* Function to return the list of files in the log directory */
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 997d4e2b482..b4702141da4 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -237,30 +237,18 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
 	/*
 	 * Create a recursive directory listing of the whole data directory.
 	 *
-	 * The WITH RECURSIVE part does most of the work. The second part gets the
-	 * targets of the symlinks in pg_tblspc directory.
+	 * Join to pg_tablespace to get the targets of the symlinks in
+	 * pg_tblspc directory.
 	 *
 	 * XXX: There is no backend function to get a symbolic link's target in
 	 * general, so if the admin has put any custom symbolic links in the data
 	 * directory, they won't be copied correctly.
 	 */
 	sql =
-		"WITH RECURSIVE files (path, filename, size, isdir) AS (\n"
-		"  SELECT '' AS path, filename, size, isdir FROM\n"
-		"  (SELECT pg_ls_dir('.', true, false) AS filename) AS fn,\n"
-		"        pg_stat_file(fn.filename, true) AS this\n"
-		"  UNION ALL\n"
-		"  SELECT parent.path || parent.filename || '/' AS path,\n"
-		"         fn, this.size, this.isdir\n"
-		"  FROM files AS parent,\n"
-		"       pg_ls_dir(parent.path || parent.filename, true, false) AS fn,\n"
-		"       pg_stat_file(parent.path || parent.filename || '/' || fn, true) AS this\n"
-		"       WHERE parent.isdir = 't'\n"
-		")\n"
-		"SELECT path || filename, size, isdir,\n"
+		"SELECT path, size, type='d' AS isdir,\n"
 		"       pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
-		"FROM files\n"
-		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n"
+		"FROM pg_ls_dir_metadata('.', true, false, true) files\n"
+		"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
 		"                             AND oid::text = files.filename\n";
 	res = PQexec(conn, sql);
 
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 60e32347880..ca938b5d138 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -163,7 +163,10 @@ sub start_primary
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text)
 		  TO rewind_user;
 		GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint, boolean)
-		  TO rewind_user;");
+		  TO rewind_user;
+		GRANT EXECUTE ON function pg_catalog.pg_ls_dir_metadata(text, bool, bool, bool)
+		  TO rewind_user;
+		");
 
 	#### Now run the test-specific parts to initialize the primary before setting
 	# up standby
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b81fbd04bc4..ef731a0e86f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11663,16 +11663,22 @@
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
-  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+  proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,i,i,i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,recurse,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
-  proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char,text}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,type,path}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
+{ oid => '8449', descr => 'list all files in a directory recursively',
+  proname => 'pg_ls_dir_recurse_sql', prorows => '10000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+  proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+  prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1695bf4a3a7..df77816e595 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -322,17 +322,37 @@ select filename, type from pg_ls_dir_metadata('.') where filename='.';
  .        | d
 (1 row)
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
  filename | type 
 ----------+------
 (0 rows)
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | type 
-----------+------+--------+--------------+--------+----------+------
+ filename | size | access | modification | change | creation | type | path 
+----------+------+--------+--------------+--------+----------+------+------
 (0 rows)
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+          path          |    filename     | type 
+------------------------+-----------------+------
+ PG_VERSION             | PG_VERSION      | -
+ base                   | base            | d
+ base/pgsql_tmp         | pgsql_tmp       | d
+ global                 | global          | d
+ global/pg_control      | pg_control      | -
+ global/pg_filenode.map | pg_filenode.map | -
+ pg_multixact           | pg_multixact    | d
+ pg_multixact/members   | members         | d
+ pg_multixact/offsets   | offsets         | d
+ pg_wal                 | pg_wal          | d
+ pg_wal/archive_status  | archive_status  | d
+(11 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 54543c06efe..971ef0e0283 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -115,11 +115,17 @@ select * from pg_ls_tmpdir() where name='Does not exist';
 
 select filename, type from pg_ls_dir_metadata('.') where filename='.';
 
-select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type from pg_ls_dir_metadata('.', false, false, false) where filename='.'; -- include_dot_dirs=false
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
 
+-- Exercise recursion
+select path, filename, type from pg_ls_dir_metadata('.', true, false, true) where
+path in ('base', 'base/pgsql_tmp', 'global', 'global/pg_control', 'global/pg_filenode.map', 'PG_VERSION', 'pg_multixact', 'pg_multixact/members', 'pg_multixact/offsets', 'pg_wal', 'pg_wal/archive_status')
+-- (type='d' or path~'^(global/.*|PG_VERSION|postmaster\.opts|postmaster\.pid|pg_logical/replorigin_checkpoint)$') and filename!~'[0-9]'
+order by path collate "C", filename collate "C";
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.1

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-09 16:50                                                                 ` Justin Pryzby <pryzby@telsasoft.com>
  2022-03-10 08:45                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-03-12 09:13                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-03-13 00:45                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-14 04:53                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-22 01:28                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Andres Freund <andres@anarazel.de>
  1 sibling, 5 replies; 70+ messages in thread

From: Justin Pryzby @ 2022-03-09 16:50 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>; Michael Paquier <michael@paquier.xyz>

Rebased over 9e9858389 (Michael may want to look at the tuplestore part?).

Fixing a comment typo.

I also changed pg_ls_dir_recurse() to handle concurrent removal of a dir, which
I noticed caused an infrequent failure on CI.  However I'm not including that
here, since the 2nd half of the patch set seems isn't ready due to lstat() on
windows.

Attachments:

  [text/x-diff] v34-0001-Document-historic-behavior-of-links-to-directori.patch (1.1K, ../../20220309165044.GB27651@telsasoft.com/2-v34-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 47dde043c27840ef43c037f968b268270dc3206d Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v34 01/15] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8a802fb2253..0be4743e3ed 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27618,7 +27618,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users
-- 
2.17.1

  [text/x-diff] v34-0002-Add-tests-before-changing-pg_ls_.patch (3.4K, ../../20220309165044.GB27651@telsasoft.com/3-v34-0002-Add-tests-before-changing-pg_ls_.patch)
  download | inline diff:
From d263f772faa6d76fdf301664bc0b436ec417b2cb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v34 02/15] Add tests before changing pg_ls_*

---
 src/test/regress/expected/misc_functions.out | 59 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      | 15 +++++
 2 files changed, 74 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 8567fcc2b45..b08e7c4a6d0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -393,6 +393,65 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalmapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalsnapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_replslotdir('') limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_tmpdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_waldir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 3db3f8bade2..ebb0352ac68 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -132,6 +132,21 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+select * from pg_ls_logdir() limit 0;
+select * from pg_ls_logicalmapdir() limit 0;
+select * from pg_ls_logicalsnapdir() limit 0;
+select * from pg_ls_replslotdir('') limit 0;
+select * from pg_ls_tmpdir() limit 0;
+select * from pg_ls_waldir() limit 0;
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.1

  [text/x-diff] v34-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (17.8K, ../../20220309165044.GB27651@telsasoft.com/4-v34-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 1c60ac0976f898eacd24881348b5f04b06dc7e1f Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v34 03/15] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 203 ++++++++++++-------
 src/include/catalog/pg_proc.dat              |  12 ++
 src/test/regress/expected/misc_functions.out |  24 +++
 src/test/regress/expected/tablespace.out     |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 src/test/regress/sql/tablespace.sql          |   5 +
 8 files changed, 215 insertions(+), 70 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0be4743e3ed..c2ff924d021 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25976,6 +25976,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 81bac6f5812..b4d3609cce7 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -700,6 +700,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1ed01620a1b..1878bac7eb3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything beginning with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -446,6 +461,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -473,54 +493,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	DIR		   *dirdesc;
-	struct dirent *de;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
-							 values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -533,23 +508,53 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	DIR		   *dirdesc;
 	struct dirent *de;
 
-	SetSingleFuncCall(fcinfo, 0);
+	/* isdir depends on metadata */
+	Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
+
+	if (flags & LS_DIR_METADATA)
+		SetSingleFuncCall(fcinfo, 0);
+	else
+		SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
 
 	/*
 	 * Now walk the directory.  Note that we must do this within a single SRF
@@ -560,20 +565,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -588,13 +600,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+				else
+#endif
+					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -608,14 +642,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -633,7 +667,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -662,7 +697,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
 
 /*
@@ -671,7 +734,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
 }
 
 /*
@@ -680,7 +743,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
 }
 
 /*
@@ -705,5 +768,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, false);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8e8715ed1c..43ee0cdc454 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11677,6 +11677,18 @@
   proargmodes => '{i,o,o,o}',
   proargnames => '{slot_name,name,size,modification}',
   prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b08e7c4a6d0..d18f401248a 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -452,6 +452,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 2dfbcfdebe1..c6b9074ab02 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -24,6 +24,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
 DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index ebb0352ac68..05794e1ae45 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -147,6 +147,17 @@ select * from pg_ls_tmpdir() limit 0;
 select * from pg_ls_waldir() limit 0;
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 896f05cea32..3aadac8b611 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -23,6 +23,11 @@ DROP TABLESPACE regress_tblspacewith;
 -- create a tablespace we can use
 CREATE TABLESPACE regress_tblspace LOCATION '';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v34-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.6K, ../../20220309165044.GB27651@telsasoft.com/5-v34-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 5e1a616e5794f322caf7ae08ebdd4c288e7ebbf3 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v34 04/15] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  8 ++++----
 src/test/regress/expected/tablespace.out     |  4 ++--
 5 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c2ff924d021..6697ab14b14 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27550,16 +27550,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1878bac7eb3..1710ebe489b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -668,7 +668,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 43ee0cdc454..19d062a36dc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11647,13 +11647,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index d18f401248a..6c64cd6f998 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -438,8 +438,8 @@ select * from pg_ls_replslotdir('') limit 0;
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
@@ -455,8 +455,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c6b9074ab02..20a7245d5e1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,8 +28,8 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

  [text/x-diff] v34-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (13.2K, ../../20220309165044.GB27651@telsasoft.com/6-v34-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From f44e30ef716a5201d009126d87677f687703d495 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v34 05/15] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir, ...

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 21 +++++-------
 src/include/catalog/pg_proc.dat              | 26 +++++++-------
 src/test/regress/expected/misc_functions.out | 28 +++++++--------
 4 files changed, 57 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6697ab14b14..88529184dd4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25987,7 +25987,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27405,12 +27406,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27428,13 +27431,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
@@ -27525,13 +27529,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1710ebe489b..1c84be69e91 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -47,11 +47,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -642,14 +639,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -668,7 +665,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -698,7 +695,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -734,7 +731,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_COMMON);
 }
 
 /*
@@ -743,7 +740,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_COMMON);
 }
 
 /*
@@ -768,5 +765,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_COMMON);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 19d062a36dc..6bacabc61ba 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11631,18 +11631,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -11659,23 +11659,23 @@
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{slot_name,name,size,modification}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{slot_name,name,size,modification,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 6c64cd6f998..0599a745270 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -413,28 +413,28 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
@@ -443,8 +443,8 @@ select * from pg_ls_tmpdir() limit 0;
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
-- 
2.17.1

  [text/x-diff] v34-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20220309165044.GB27651@telsasoft.com/7-v34-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 7712339b798185f205d99dc79559f9026abd9bd3 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v34 06/15] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1c84be69e91..80516833e33 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -639,7 +639,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 20a7245d5e1..50a3a13fbea 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -32,6 +32,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 3aadac8b611..034c903b41c 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -28,6 +28,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v34-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (21.6K, ../../20220309165044.GB27651@telsasoft.com/8-v34-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 8e5c512b15d5cabaad71173fd8c7f9b45fe3a7ad Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v34 07/15] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 ++++++---
 src/backend/utils/adt/genfile.c              | 72 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 42 ++++++------
 src/test/regress/expected/misc_functions.out | 40 +++++------
 src/test/regress/expected/tablespace.out     |  8 +--
 5 files changed, 102 insertions(+), 94 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 88529184dd4..05d507f579f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25987,7 +25987,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -27406,13 +27409,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27431,13 +27437,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -27529,14 +27538,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27556,13 +27568,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 80516833e33..698468f9860 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -394,6 +396,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -404,7 +428,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -444,27 +468,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -569,8 +575,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -609,24 +615,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-				else
-#endif
-					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6bacabc61ba..03839b35ea4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11631,63 +11631,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargmodes => '{i,o,o,o,o}',
-  proargnames => '{slot_name,name,size,modification,isdir}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0599a745270..cb784ece778 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -413,38 +413,38 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
@@ -455,8 +455,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -472,8 +472,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 50a3a13fbea..b01ca30c7ab 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,15 +28,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-10 08:45                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
  4 siblings, 0 replies; 70+ messages in thread

From: Fabien COELHO @ 2022-03-10 08:45 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>; Michael Paquier <michael@paquier.xyz>


Hello Justin,

I hope to look at it over the week-end.

-- 
Fabien Coelho - CRI, MINES ParisTech





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-12 09:13                                                                   ` Fabien COELHO <coelho@cri.ensmp.fr>
  4 siblings, 0 replies; 70+ messages in thread

From: Fabien COELHO @ 2022-03-12 09:13 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>; Michael Paquier <michael@paquier.xyz>


Hello Justin,

Review about v34, up from v32 which I reviewed in January. v34 is an 
updated version of v32, without the part about lstat at the end of the 
series.

All 7 patches apply cleanly.

# part 01

One liner doc improvement about the directory flag.

OK.

# part 02

Add tests for various options on pg_ls_dir, and for pg_stat_file, which were not
exercised before.  "make check" is ok.

OK.

# part 03

This patch adds a new pg_ls_dir_metadata.  Internally, this is an extension of
pg_ls_dir_files function which is used by other pg_ls functions.  Doc ok.

New tests are added which check that the result columns are configured as required,
including error cases.

"make check" is ok.

OK.

# part 04

Add a new "isdir" column to "pg_ls_tmpdir" output.  This is a small behavioral
change.

I'm ok with that, however I must say that I'm still unsure why we would 
not jump directly to a "type" char column.  What is wrong with outputing 
'd' or '-' instead of true or false? This way, the interface needs not 
change if "lstat" is used later? ISTM that the interface issue should be 
somehow independent of the implementation issue, and we should choose 
directly the right/best interface?

Independently, the documentation may be clearer about what happens to 
"isdir" when the file is a link? It may say that the behavior may change 
in the future?

About homogeneity, I note that some people may be against adding "isdir" 
to other ls functions. I must say that I cannot see a good argument not to 
do it, and that I hate dealing with systems which are not homogeneous 
because it creates surprises and some loss of time.

"make check" ok.

OK.

# part 05

Add isdir to other ls functions. Doc is updated.

Same as above, I'd prefer a char instead of a bool, as it is more extendable and
future-proof.

OK.

# part 06

This part extends and adds a test for pg_ls_logdir.
"make check" is ok.

OK.

# part 07

This part extends pg_stat_file with more date informations.

"make check" is ok.

OK.

# doc

Overall doc generation is OK.

-- 
Fabien.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-13 00:45                                                                   ` Michael Paquier <michael@paquier.xyz>
  2022-03-13 00:56                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  4 siblings, 1 reply; 70+ messages in thread

From: Michael Paquier @ 2022-03-13 00:45 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Wed, Mar 09, 2022 at 10:50:45AM -0600, Justin Pryzby wrote:
> Rebased over 9e9858389 (Michael may want to look at the tuplestore part?).

Are you referring to the contents of 0003 here that changes the
semantics of pg_ls_dir_files() regarding its setup call?
--
Michael

Attachments:

  [application/pgp-signature] signature.asc (832B, ../../Yi0+r0651McAo+mR@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-13 00:45                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
@ 2022-03-13 00:56                                                                     ` Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 0 replies; 70+ messages in thread

From: Justin Pryzby @ 2022-03-13 00:56 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Sun, Mar 13, 2022 at 09:45:35AM +0900, Michael Paquier wrote:
> On Wed, Mar 09, 2022 at 10:50:45AM -0600, Justin Pryzby wrote:
> > Rebased over 9e9858389 (Michael may want to look at the tuplestore part?).
> 
> Are you referring to the contents of 0003 here that changes the
> semantics of pg_ls_dir_files() regarding its setup call?

Yes, as it has this:

-       SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);                                                                                                                                                                       
...
-       SetSingleFuncCall(fcinfo, 0);                                                                                                                                                                                             
...
+       if (flags & LS_DIR_METADATA)                                                                                                                                                                                              
+               SetSingleFuncCall(fcinfo, 0);                                                                                                                                                                                     
+       else                                                                                                                                                                                                                      
+               SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);                                                                                                                                                               

-- 
Justin





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-14 04:53                                                                   ` Michael Paquier <michael@paquier.xyz>
  2022-03-15 01:59                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-15 02:37                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  4 siblings, 2 replies; 70+ messages in thread

From: Michael Paquier @ 2022-03-14 04:53 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Wed, Mar 09, 2022 at 10:50:45AM -0600, Justin Pryzby wrote:
> I also changed pg_ls_dir_recurse() to handle concurrent removal of a dir, which
> I noticed caused an infrequent failure on CI.  However I'm not including that
> here, since the 2nd half of the patch set seems isn't ready due to lstat() on
> windows.

lstat() has been a subject of many issues over the years with our
internal emulation and issues related to its concurrency, but we use
it in various areas of the in-core code, so that does not sound like
an issue to me.  It depends on what you want to do with it in
genfile.c and which data you'd expect, in addition to the detection of
junction points for WIN32, I guess.  v34 has no references to
pg_ls_dir_recurse(), but that's a WITH RECURSIVE, so we would not
really need it, do we?

@@ -27618,7 +27618,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         Returns a record containing the file's size, last access time stamp,
         last modification time stamp, last file status change time stamp (Unix
         platforms only), file creation time stamp (Windows only), and a flag
-        indicating if it is a directory.
+        indicating if it is a directory (or a symbolic link to a directory).
        </para>
        <para>
         This function is restricted to superusers by default, but other users

This is from 0001, and this addition in the documentation is not
completely right.  As pg_stat_file() uses stat() to get back the
information of a file/directory, we'd just follow the link if
specifying one in the input argument.  We could say instead, if we
were to improve the docs, that "If filename is a link, this function
returns information about the file or directory the link refers to."
I would put that as a different paragraph.

+select * from pg_ls_archive_statusdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)

FWIW, this one is fine as of ValidateXLOGDirectoryStructure() that
would make sure archive_status exists before any connection is
attempted to the cluster.

> +select * from pg_ls_logdir() limit 0;

This test on pg_ls_logdir() would fail if running installcheck on a
cluster that has logging_collector disabled.  So this cannot be
included.

+select * from pg_ls_logicalmapdir() limit 0;
+select * from pg_ls_logicalsnapdir() limit 0;
+select * from pg_ls_replslotdir('') limit 0;
+select * from pg_ls_tmpdir() limit 0;
+select * from pg_ls_waldir() limit 0;
+select * from pg_stat_file('.') limit 0;

The rest of the patch set should be stable AFAIK, there are various
steps when running a checkpoint that makes sure that any of these
exist, without caring about the value of wal_level.

+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>

What is metadata in this case?  (I have read the code and know what
you mean, but folks only looking at the documentation may be puzzled
by that).  It could be cleaner to use the same tupledesc for any
callers of this function, and return NULL in cases these are not 
adapted.

+   /* check the optional arguments */
+   if (PG_NARGS() == 3)
+   {
+       if (!PG_ARGISNULL(1))
+       {
+           if (PG_GETARG_BOOL(1))
+               flags |= LS_DIR_MISSING_OK;
+           else
+               flags &= ~LS_DIR_MISSING_OK;
+       }
+
+       if (!PG_ARGISNULL(2))
+       {
+           if (PG_GETARG_BOOL(2))
+               flags &= ~LS_DIR_SKIP_DOT_DIRS;
+           else
+               flags |= LS_DIR_SKIP_DOT_DIRS;
+       }
+   }

The subtle different between the false and true code paths of those
arguments 1 and 2 had better be explained?  The bit-wise operations
are slightly different in both cases, so it is not clear which part
does what, and what's the purpose of this logic.

-   SetSingleFuncCall(fcinfo, 0);
+   /* isdir depends on metadata */
+   Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+   /* Unreasonable to show isdir and skip dirs */
+   Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));

Incorrect code format.  Spaces required.

+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to
succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to
completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist,
rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE
b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1)
AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';

So, here, we have a test that may not actually test what we want to
test.

Hmm.  I am not convinced that we need a new set of SQL functions as
presented in 0003 (addition of meta-data in pg_ls_dir()), and
extensions of 0004 (do the same but for pg_ls_tmpdir) and 0005 (same
for the other pg_ls* functions).  The changes of pg_ls_dir_files()
make in my opinion the code harder to follow as the resulting tuple 
size depends on the type of flag used, and you can already retrieve
the rest of the information with a join, probably LATERAL, on
pg_stat_file(), no?  The same can be said about 0007, actually.

The addition of isdir for any of the paths related to pg_logical/ and
the replication slots has also a limited interest, because we know
already those contents, and these are not directories as far as I
recall.

0006 invokes a behavior change for pg_ls_logdir(), where it makes
sense to me to fail if the directory does not exist, so I am not in
favor of that.

In the whole set, improving the docs as of 0001 makes sense, but the
change is incomplete.  Most of 0002 also makes sense and should be
stable enough.  I am less enthusiastic about any of the other changes
proposed and what we can gain from these parts.
--
Michael

Attachments:

  [application/pgp-signature] signature.asc (832B, ../../Yi7KYhyweiLf6XXc@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-14 04:53                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
@ 2022-03-15 01:59                                                                     ` Michael Paquier <michael@paquier.xyz>
  1 sibling, 0 replies; 70+ messages in thread

From: Michael Paquier @ 2022-03-15 01:59 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Mon, Mar 14, 2022 at 01:53:54PM +0900, Michael Paquier wrote:
> +select * from pg_ls_logicalmapdir() limit 0;
> +select * from pg_ls_logicalsnapdir() limit 0;
> +select * from pg_ls_replslotdir('') limit 0;
> +select * from pg_ls_tmpdir() limit 0;
> +select * from pg_ls_waldir() limit 0;
> +select * from pg_stat_file('.') limit 0;
> 
> The rest of the patch set should be stable AFAIK, there are various
> steps when running a checkpoint that makes sure that any of these
> exist, without caring about the value of wal_level.

I was contemplating at 0002 this morning, so see which parts would be
independently useful, and got reminded that we already check the
execution of all those functions in other regression tests, like
test_decoding for the replication slot ones and misc_functions.sql for
the more critical ones, so those extra queries would be just
interesting to check the shape of their SRFs, which is related to the
other patches of the set and limited based on my arguments from
yesterday.

There was one thing that stood out though: there was nothing for the
two options of pg_ls_dir(), called missing_ok and include_dot_dirs.
missing_ok is embedded in one query of pg_rewind, but this is a
counter-measure against concurrent file removals so we cannot rely on
pg_rewind to check that.  And the second option was not run at all.

I have extracted both test cases after rewriting them a bit, and
applied that separately.
--
Michael

Attachments:

  [application/pgp-signature] signature.asc (832B, ../../Yi%2Fy9a1N3r1zz%2FzM@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-14 04:53                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
@ 2022-03-15 02:37                                                                     ` Justin Pryzby <pryzby@telsasoft.com>
  2022-03-15 02:44                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-31 23:42                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 2 replies; 70+ messages in thread

From: Justin Pryzby @ 2022-03-15 02:37 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Mon, Mar 14, 2022 at 01:53:54PM +0900, Michael Paquier wrote:
> On Wed, Mar 09, 2022 at 10:50:45AM -0600, Justin Pryzby wrote:
> > I also changed pg_ls_dir_recurse() to handle concurrent removal of a dir, which
> > I noticed caused an infrequent failure on CI.  However I'm not including that
> > here, since the 2nd half of the patch set seems isn't ready due to lstat() on
> > windows.
> 
> lstat() has been a subject of many issues over the years with our
> internal emulation and issues related to its concurrency, but we use
> it in various areas of the in-core code, so that does not sound like
> an issue to me.  It depends on what you want to do with it in
> genfile.c and which data you'd expect, in addition to the detection of
> junction points for WIN32, I guess.

To avoid cycles, a recursion function would need to know whether to recurse
into a directory or to output that something is isdir=false or type=link, and
avoid recursing into it. 

> pg_ls_dir_recurse(), but that's a WITH RECURSIVE, so we would not
> really need it, do we?

Tom disliked it when I had written it as a recursive CTE, so I rewrote it in C.
129225.1606166058@sss.pgh.pa.us

> Hmm.  I am not convinced that we need a new set of SQL functions as
> presented in 0003 (addition of meta-data in pg_ls_dir()), and
> extensions of 0004 (do the same but for pg_ls_tmpdir) and 0005 (same
> for the other pg_ls* functions).  The changes of pg_ls_dir_files()
> make in my opinion the code harder to follow as the resulting tuple 
> size depends on the type of flag used, and you can already retrieve
> the rest of the information with a join, probably LATERAL, on
> pg_stat_file(), no?  The same can be said about 0007, actually.

Yes, one can get the same information with a lateral join (as I said 2 years
ago).  But it's more helpful to provide a function, rather than leave that to
people to figure out - possibly incorrectly, or badly, like by parsing the
output of COPY FROM PROGRAM 'ls -l'.  The query to handle tablespaces is
particularly obscure:
20200310183037.GA29065@telsasoft.com
20201223191710.GR30237@telsasoft.com

One could argue that most of the pg_ls_* functions aren't needed (including
1922d7c6e), since the same things are possible with pg_ls_dir() and
pg_stat_file().
|1922d7c6e Add SQL functions to monitor the directory contents of replication slots

The original, minimal goal of this patch was to show shared tempdirs in
pg_ls_tmpfile() - rather than hiding them misleadingly as currently happens.
20200310183037.GA29065@telsasoft.com
20200313131232.GO29065@telsasoft.com

I added the metadata function 2 years ago since it's silly to show metadata for
tmpdir but not other, arbitrary directories.
20200310183037.GA29065@telsasoft.com
20200313131232.GO29065@telsasoft.com
20201223191710.GR30237@telsasoft.com

> The addition of isdir for any of the paths related to pg_logical/ and
> the replication slots has also a limited interest, because we know
> already those contents, and these are not directories as far as I
> recall.

Except when we don't, since extensions can do things that core doesn't, as
Fabien pointed out.
alpine.DEB.2.21.2001160927390.30419@pseudo

> In the whole set, improving the docs as of 0001 makes sense, but the
> change is incomplete.  Most of 0002 also makes sense and should be
> stable enough.  I am less enthusiastic about any of the other changes
> proposed and what we can gain from these parts.

It is frustrating to hear this feedback now, after the patch has gone through
multiple rewrites over 2 years - based on other positive feedback and review.
I went to the effort to ask, numerous times, whether to write the patch and how
its interfaces should look.  Now, I'm hearing that not only the implementation
but its goals are wrong.  What should I have done to avoid that ?

20200503024215.GJ28974@telsasoft.com
20191227195918.GF12890@telsasoft.com
20200116003924.GJ26045@telsasoft.com
20200908195126.GB18552@telsasoft.com





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-14 04:53                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-15 02:37                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-15 02:44                                                                       ` Michael Paquier <michael@paquier.xyz>
  1 sibling, 0 replies; 70+ messages in thread

From: Michael Paquier @ 2022-03-15 02:44 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Mon, Mar 14, 2022 at 09:37:25PM -0500, Justin Pryzby wrote:
> One could argue that most of the pg_ls_* functions aren't needed (including
> 1922d7c6e), since the same things are possible with pg_ls_dir() and
> pg_stat_file().
> |1922d7c6e Add SQL functions to monitor the directory contents of replication slots

This main argument behind this one is monitoring, as the execution to
those functions can be granted at a granular level depending on the
roles doing the disk space lookups.
--
Michael

Attachments:

  [application/pgp-signature] signature.asc (832B, ../../Yi%2F9kjzMu3%2FXm6Qx@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-14 04:53                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-15 02:37                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-31 23:42                                                                       ` Justin Pryzby <pryzby@telsasoft.com>
  2022-04-03 13:39                                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Greg Stark <stark@mit.edu>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2022-03-31 23:42 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>; Andres Freund <andres@anarazel.de>

On Mon, Mar 14, 2022 at 09:37:25PM -0500, Justin Pryzby wrote:
> The original, minimal goal of this patch was to show shared tempdirs in
> pg_ls_tmpfile() - rather than hiding them misleadingly as currently happens.
> 20200310183037.GA29065@telsasoft.com
> 20200313131232.GO29065@telsasoft.com
> 
> I added the metadata function 2 years ago since it's silly to show metadata for
> tmpdir but not other, arbitrary directories.
> 20200310183037.GA29065@telsasoft.com
> 20200313131232.GO29065@telsasoft.com
> 20201223191710.GR30237@telsasoft.com

I renamed the CF entry to make even more clear the original motive for the
patches (I'm not maintaining the patch to add the metadata function just to
avoid writing a lateral join).

> > In the whole set, improving the docs as of 0001 makes sense, but the
> > change is incomplete.  Most of 0002 also makes sense and should be
> > stable enough.  I am less enthusiastic about any of the other changes
> > proposed and what we can gain from these parts.
> 
> It is frustrating to hear this feedback now, after the patch has gone through
> multiple rewrites over 2 years - based on other positive feedback and review.
> I went to the effort to ask, numerous times, whether to write the patch and how
> its interfaces should look.  Now, I'm hearing that not only the implementation
> but its goals are wrong.  What should I have done to avoid that ?
> 
> 20200503024215.GJ28974@telsasoft.com
> 20191227195918.GF12890@telsasoft.com
> 20200116003924.GJ26045@telsasoft.com
> 20200908195126.GB18552@telsasoft.com

Michael said he's not enthusiastic about the patch.  But I haven't heard a
suggestion about how else to address the issue that pg_ls_tmpdir() hides shared
filesets.

On Mon, Mar 28, 2022 at 09:13:52PM -0500, Justin Pryzby wrote:
> On Sat, Mar 26, 2022 at 08:23:54PM +0900, Michael Paquier wrote:
> > On Wed, Mar 23, 2022 at 03:17:35PM +0900, Michael Paquier wrote:
> > > FWIW, per my review the bit of the patch set that I found the most
> > > relevant is the addition of a note in the docs of pg_stat_file() about
> > > the case where "filename" is a link, because the code internally uses
> > > stat().   The function name makes that obvious, but that's not
> > > commonly known, I guess.  Please see the attached, that I would be
> > > fine to apply.
> > 
> > Hmm.  I am having second thoughts on this one, as on Windows we rely
> > on GetFileInformationByHandle() for the emulation of stat() in
> > win32stat.c, and it looks like this returns some information about the
> > junction point and not the directory or file this is pointing to, it
> > seems.
> 
> Where did you find that ?  What metadata does it return about the junction
> point ?  We only care about a handful of fields.

Pending your feedback, I didn't modify this beyond your original suggestion -
which seemed like a good one.

This also adds some comments you requested and fixes your coding style
complaints, and causes cfbot to test my proposed patch rather than your doc
patch.

-- 
Justin

Attachments:

  [text/x-diff] v35-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20220331234159.GS28503@telsasoft.com/2-v35-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 354e62f07f345de403d1b2894eba98aec44217b5 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v35 1/7] Document historic behavior of links to directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4001cb2bda5..d01aeec9f88 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27631,6 +27631,10 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         platforms only), file creation time stamp (Windows only), and a flag
         indicating if it is a directory.
        </para>
+       <para>
+       If filename is a link, this function returns information about the file
+       or directory the link refers to.
+       </para>
        <para>
         This function is restricted to superusers by default, but other users
         can be granted EXECUTE to run the function.
-- 
2.17.1

  [text/x-diff] v35-0002-Add-tests-before-changing-pg_ls_.patch (3.4K, ../../20220331234159.GS28503@telsasoft.com/3-v35-0002-Add-tests-before-changing-pg_ls_.patch)
  download | inline diff:
From ee202767ae0d6651c552056424b004be2b110ec1 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v35 2/7] Add tests before changing pg_ls_*

---
 src/test/regress/expected/misc_functions.out | 59 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      | 15 +++++
 2 files changed, 74 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 01d1ad0b9a4..45544469af5 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -416,6 +416,65 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalmapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalsnapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_replslotdir('') limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_tmpdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_waldir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 072fc36a1ff..59d6e517503 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -140,6 +140,21 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+select * from pg_ls_logdir() limit 0;
+select * from pg_ls_logicalmapdir() limit 0;
+select * from pg_ls_logicalsnapdir() limit 0;
+select * from pg_ls_replslotdir('') limit 0;
+select * from pg_ls_tmpdir() limit 0;
+select * from pg_ls_waldir() limit 0;
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.1

  [text/x-diff] v35-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (17.8K, ../../20220331234159.GS28503@telsasoft.com/4-v35-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 4ee9b0cd3a2ef362d9c91fdce31d06a5b8d6b48a Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v35 3/7] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 205 ++++++++++++-------
 src/include/catalog/pg_proc.dat              |  12 ++
 src/test/regress/expected/misc_functions.out |  24 +++
 src/test/regress/expected/tablespace.out     |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 src/test/regress/sql/tablespace.sql          |   5 +
 8 files changed, 217 insertions(+), 70 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d01aeec9f88..d2a455a3e27 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25987,6 +25987,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 81bac6f5812..b4d3609cce7 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -700,6 +700,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 88f279d1b31..75b7bf99849 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything beginning with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -446,6 +461,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -473,54 +493,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	DIR		   *dirdesc;
-	struct dirent *de;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
-							 values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -533,23 +508,55 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	DIR		   *dirdesc;
 	struct dirent *de;
 
-	SetSingleFuncCall(fcinfo, 0);
+	/* isdir depends on metadata */
+	Assert(!(flags & LS_DIR_ISDIR) || (flags & LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags & LS_DIR_ISDIR) || !(flags & LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		/* missing_ok */
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		/* include_dot_dirs */
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
+
+	if (flags & LS_DIR_METADATA)
+		SetSingleFuncCall(fcinfo, 0);
+	else
+		SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
 
 	/*
 	 * Now walk the directory.  Note that we must do this within a single SRF
@@ -560,20 +567,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -588,13 +602,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+				else
+#endif
+					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -608,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -633,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -662,7 +699,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
 
 /*
@@ -671,7 +736,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
 }
 
 /*
@@ -680,7 +745,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
 }
 
 /*
@@ -705,5 +770,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, false);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 25304430f44..7307f44f371 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11769,6 +11769,18 @@
   proargmodes => '{i,o,o,o}',
   proargnames => '{slot_name,name,size,modification}',
   prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 45544469af5..e54e38f54ad 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -475,6 +475,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf9..8159c9f18f1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -33,6 +33,14 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
  pg_tblspc/NNN
 (1 row)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 59d6e517503..fcb456f434d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -155,6 +155,17 @@ select * from pg_ls_tmpdir() limit 0;
 select * from pg_ls_waldir() limit 0;
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a8..cf683c3bf3a 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -27,6 +27,11 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN')
   FROM pg_tablespace  WHERE spcname = 'regress_tblspace';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v35-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.6K, ../../20220331234159.GS28503@telsasoft.com/5-v35-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From b864c1bbc5c95ab51036d87c5b59e82278d74bbb Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v35 4/7] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  8 ++++----
 src/test/regress/expected/tablespace.out     |  4 ++--
 5 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d2a455a3e27..3931411c01f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27561,16 +27561,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 75b7bf99849..3775cae225d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7307f44f371..142b4b8d37d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11739,13 +11739,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e54e38f54ad..77bf661627b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -461,8 +461,8 @@ select * from pg_ls_replslotdir('') limit 0;
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
@@ -478,8 +478,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 8159c9f18f1..ce5d6f73e01 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,8 +37,8 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

  [text/x-diff] v35-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (13.3K, ../../20220331234159.GS28503@telsasoft.com/6-v35-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 6fcd12b1284d8273f7692c9612d1aea8feb80973 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v35 5/7] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir, ...

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 21 +++++-------
 src/include/catalog/pg_proc.dat              | 26 +++++++-------
 src/test/regress/expected/misc_functions.out | 28 +++++++--------
 4 files changed, 57 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3931411c01f..e7c63640d7e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25998,7 +25998,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27416,12 +27417,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and roles with privileges of
@@ -27439,13 +27442,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and roles with privileges of 
@@ -27536,13 +27540,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3775cae225d..cfb7fc7e080 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -47,11 +47,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -644,14 +641,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -670,7 +667,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -700,7 +697,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +733,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_COMMON);
 }
 
 /*
@@ -745,7 +742,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_COMMON);
 }
 
 /*
@@ -770,5 +767,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_COMMON);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 142b4b8d37d..a68558cfed0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11723,18 +11723,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -11751,23 +11751,23 @@
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{slot_name,name,size,modification}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{slot_name,name,size,modification,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 77bf661627b..94b5eac47ea 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -436,28 +436,28 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
@@ -466,8 +466,8 @@ select * from pg_ls_tmpdir() limit 0;
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
-- 
2.17.1

  [text/x-diff] v35-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20220331234159.GS28503@telsasoft.com/7-v35-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From f5aabc8503ecde1bb9489cdd05b66987383e8daa Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v35 6/7] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index cfb7fc7e080..ca5223be2ee 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -641,7 +641,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index ce5d6f73e01..53662fb0efd 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -41,6 +41,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index cf683c3bf3a..18b435c9abb 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -32,6 +32,10 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v35-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (21.6K, ../../20220331234159.GS28503@telsasoft.com/8-v35-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From a0c8a5aa2166fa50b54abfd25b6792369f87bd57 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v35 7/7] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 ++++++---
 src/backend/utils/adt/genfile.c              | 72 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 42 ++++++------
 src/test/regress/expected/misc_functions.out | 40 +++++------
 src/test/regress/expected/tablespace.out     |  8 +--
 5 files changed, 102 insertions(+), 94 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e7c63640d7e..a3004148a52 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25998,7 +25998,10 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -27417,13 +27420,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27442,13 +27448,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -27540,14 +27549,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27567,13 +27579,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ca5223be2ee..c289cf1ad85 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -394,6 +396,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -404,7 +428,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -444,27 +468,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -571,8 +577,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -611,24 +617,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-				else
-#endif
-					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a68558cfed0..c7e96685e1b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11723,63 +11723,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '9858',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '9859',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '9860',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargmodes => '{i,o,o,o,o}',
-  proargnames => '{slot_name,name,size,modification,isdir}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 94b5eac47ea..afb1744bee7 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -436,38 +436,38 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
@@ -478,8 +478,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -495,8 +495,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 53662fb0efd..ef64e7bfa8b 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,15 +37,15 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-14 04:53                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-15 02:37                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-31 23:42                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-04-03 13:39                                                                         ` Greg Stark <stark@mit.edu>
  0 siblings, 0 replies; 70+ messages in thread

From: Greg Stark @ 2022-04-03 13:39 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Michael Paquier <michael@paquier.xyz>; Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers; Thomas Munro <thomas.munro@gmail.com>; Andres Freund <andres@anarazel.de>

The cfbot is failing testing this patch. It seems... unlikely given
the nature of the patch modifying an admin function that doesn't even
modify the database that it should be breaking a streaming test.
Perhaps the streaming test is using this function in the testing
scaffolding?

[03:19:29.564] # Failed test 'regression tests pass'
[03:19:29.564] # at t/027_stream_regress.pl line 76.
[03:19:29.564] # got: '256'
[03:19:29.564] # expected: '0'
[03:19:29.564] # Looks like you failed 1 test of 5.
[03:19:29.565] [03:19:27] t/027_stream_regress.pl ..............
[03:19:29.565] Dubious, test returned 1 (wstat 256, 0x100)
[03:19:29.565] Failed 1/5 subtests





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-03-22 01:28                                                                   ` Andres Freund <andres@anarazel.de>
  2022-03-23 06:17                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  4 siblings, 1 reply; 70+ messages in thread

From: Andres Freund @ 2022-03-22 01:28 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>; Michael Paquier <michael@paquier.xyz>

Hi,

On 2022-03-09 10:50:45 -0600, Justin Pryzby wrote:
> Rebased over 9e9858389 (Michael may want to look at the tuplestore part?).

Doesn't apply cleanly anymore: http://cfbot.cputube.org/patch_37_2377.log

Marked as waiting-on-author.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-22 01:28                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Andres Freund <andres@anarazel.de>
@ 2022-03-23 06:17                                                                     ` Michael Paquier <michael@paquier.xyz>
  2022-03-26 11:23                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  0 siblings, 1 reply; 70+ messages in thread

From: Michael Paquier @ 2022-03-23 06:17 UTC (permalink / raw)
  To: Andres Freund <andres@anarazel.de>; +Cc: Justin Pryzby <pryzby@telsasoft.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Mon, Mar 21, 2022 at 06:28:28PM -0700, Andres Freund wrote:
> Doesn't apply cleanly anymore: http://cfbot.cputube.org/patch_37_2377.log
> 
> Marked as waiting-on-author.

FWIW, per my review the bit of the patch set that I found the most
relevant is the addition of a note in the docs of pg_stat_file() about
the case where "filename" is a link, because the code internally uses
stat().   The function name makes that obvious, but that's not
commonly known, I guess.  Please see the attached, that I would be
fine to apply.
--
Michael

Attachments:

  [text/x-diff] doc-pgstatfile.patch (709B, ../../Yjq7f5VHRPtwvs6E@paquier.xyz/2-doc-pgstatfile.patch)
  download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8a802fb225..ca111f0745 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27620,6 +27620,10 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         platforms only), file creation time stamp (Windows only), and a flag
         indicating if it is a directory.
        </para>
+       <para>
+        If <parameter>filename</parameter> is a link, this function returns
+        information about the file or directory the link refers to.
+       </para>
        <para>
         This function is restricted to superusers by default, but other users
         can be granted EXECUTE to run the function.

  [application/pgp-signature] signature.asc (832B, ../../Yjq7f5VHRPtwvs6E@paquier.xyz/3-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-22 01:28                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Andres Freund <andres@anarazel.de>
  2022-03-23 06:17                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
@ 2022-03-26 11:23                                                                       ` Michael Paquier <michael@paquier.xyz>
  2022-03-29 02:13                                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Michael Paquier @ 2022-03-26 11:23 UTC (permalink / raw)
  To: Andres Freund <andres@anarazel.de>; +Cc: Justin Pryzby <pryzby@telsasoft.com>; Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Wed, Mar 23, 2022 at 03:17:35PM +0900, Michael Paquier wrote:
> FWIW, per my review the bit of the patch set that I found the most
> relevant is the addition of a note in the docs of pg_stat_file() about
> the case where "filename" is a link, because the code internally uses
> stat().   The function name makes that obvious, but that's not
> commonly known, I guess.  Please see the attached, that I would be
> fine to apply.

Hmm.  I am having second thoughts on this one, as on Windows we rely
on GetFileInformationByHandle() for the emulation of stat() in
win32stat.c, and it looks like this returns some information about the
junction point and not the directory or file this is pointing to, it
seems.  At the end, it looks better to keep things simple here, so
let's drop it.
--
Michael

Attachments:

  [application/pgp-signature] signature.asc (832B, ../../Yj73yraY43IhgwiK@paquier.xyz/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-03-22 01:28                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Andres Freund <andres@anarazel.de>
  2022-03-23 06:17                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
  2022-03-26 11:23                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
@ 2022-03-29 02:13                                                                         ` Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 0 replies; 70+ messages in thread

From: Justin Pryzby @ 2022-03-29 02:13 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; +Cc: Andres Freund <andres@anarazel.de>; Fabien COELHO <coelho@cri.ensmp.fr>; Bossart, Nathan <bossartn@amazon.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>; pgsql-hackers@postgresql.org, Thomas Munro <thomas.munro@gmail.com>

On Sat, Mar 26, 2022 at 08:23:54PM +0900, Michael Paquier wrote:
> On Wed, Mar 23, 2022 at 03:17:35PM +0900, Michael Paquier wrote:
> > FWIW, per my review the bit of the patch set that I found the most
> > relevant is the addition of a note in the docs of pg_stat_file() about
> > the case where "filename" is a link, because the code internally uses
> > stat().   The function name makes that obvious, but that's not
> > commonly known, I guess.  Please see the attached, that I would be
> > fine to apply.
> 
> Hmm.  I am having second thoughts on this one, as on Windows we rely
> on GetFileInformationByHandle() for the emulation of stat() in
> win32stat.c, and it looks like this returns some information about the
> junction point and not the directory or file this is pointing to, it
> seems.

Where did you find that ?  What metadata does it return about the junction
point ?  We only care about a handful of fields.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-06-24 04:35                                                                 ` Justin Pryzby <pryzby@telsasoft.com>
  2022-10-28 00:38                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  1 sibling, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2022-06-24 04:35 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; +Cc: pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>

This thread has been going for 2.5 years, so here's a(nother) recap.

This omits the patches for recursion, since they're optional and evidently a
distraction from the main patches.

On Fri, Dec 27, 2019 at 11:02:20AM -0600, Justin Pryzby wrote:
> The goal is to somehow show tmpfiles (or at least dirs) used by parallel
> workers.

On Thu, Jan 16, 2020 at 08:38:46AM -0600, Justin Pryzby wrote:
> I think if someone wants the full generality, they can do this:
> 
> postgres=# SELECT name, s.size, s.modification, s.isdir FROM (SELECT 'base/pgsql_tmp'p)p, pg_ls_dir(p)name, pg_stat_file(p||'/'||name)s;
>  name | size |      modification      | isdir 
> ------+------+------------------------+-------
>  .foo | 4096 | 2020-01-16 08:57:04-05 | t
> 
> In my mind, pg_ls_tmpdir() is for showing tmpfiles, not just a shortcut to
> SELECT pg_ls_dir((SELECT 'base/pgsql_tmp'p)); -- or, for all tablespaces:
> WITH x AS (SELECT format('/PG_%s_%s', split_part(current_setting('server_version'), '.', 1), catalog_version_no) suffix FROM pg_control_system()), y AS (SELECT a, pg_ls_dir(a) AS d FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(oid),'')||suffix, 'base') a FROM pg_tablespace,x)a) SELECT a, pg_ls_dir(a||'/pgsql_tmp') FROM y WHERE d='pgsql_tmp';

On Tue, Mar 10, 2020 at 01:30:37PM -0500, Justin Pryzby wrote:
> I took a step back, and I wondered whether we should add a generic function for
> listing a dir with metadata, possibly instead of changing the existing
> functions.  Then one could do pg_ls_dir_metadata('pg_wal',false,false);
> 
> Since pg8.1, we have pg_ls_dir() to show a list of files.  Since pg10, we've
> had pg_ls_logdir and pg_ls_waldir, which show not only file names but also
> (some) metadata (size, mtime).  And since pg12, we've had pg_ls_tmpfile and
> pg_ls_archive_statusdir, which also show metadata.
> 
> ...but there's no a function which lists the metadata of an directory other
> than tmp, wal, log.
> 
> One can do this:
> |SELECT b.*, c.* FROM (SELECT 'base' a)a, LATERAL (SELECT a||'/'||pg_ls_dir(a.a)b)b, pg_stat_file(b)c;
> ..but that's not as helpful as allowing:
> |SELECT * FROM pg_ls_dir_metadata('.',true,true);
> 
> There's also no function which recurses into an arbitrary directory, so it
> seems shortsighted to provide a function to recursively list a tmpdir.
> 
> Also, since pg_ls_dir_metadata indicates whether the path is a dir, one can
> write a SQL function to show the dir recursively.  It'd be trivial to plug in
> wal/log/tmp (it seems like tmpdirs of other tablespace's are not entirely
> trivial).
> |SELECT * FROM pg_ls_dir_recurse('base/pgsql_tmp');

> It's pretty unfortunate if a function called
> pg_ls_tmpdir hides shared filesets, so maybe it really is best to change that
> (it's new in v12).

On Fri, Mar 13, 2020 at 08:12:32AM -0500, Justin Pryzby wrote:
> The merge conflict presents another opportunity to solicit comments on the new
> approach.  Rather than making "recurse into tmpdir" the end goal:
> 
>   - add a function to show metadata of an arbitrary dir;
>   - add isdir arguments to pg_ls_* functions (including pg_ls_tmpdir but not
>     pg_ls_dir).
>   - maybe add pg_ls_dir_recurse, which satisfies the original need;
>   - retire pg_ls_dir (does this work with tuplestore?)
>   - profit
> 
> The alternative seems to be to go back to Alvaro's earlier proposal:
>  - not only add "isdir", but also recurse;
> 
> I think I would insist on adding a general function to recurse into any dir.
> And *optionally* change ps_ls_* to recurse (either by accepting an argument, or
> by making that a separate patch to debate).

On Tue, Mar 31, 2020 at 03:08:12PM -0500, Justin Pryzby wrote:
> The patch intends to fix the issue of "failing to show failed filesets"
> (because dirs are skipped) while also generalizing existing functions (to show
> directories and "isdir" column) and providing some more flexible ones (to list
> file and metadata of a dir, which is currently possible [only] for "special"
> directories, or by recursively calling pg_stat_file).

On Wed, Dec 23, 2020 at 01:17:10PM -0600, Justin Pryzby wrote:
> However, pg_ls_tmpdir is special since it handles tablespace tmpdirs, which it
> seems is not trivial to get from sql:
> 
> +SELECT * FROM (SELECT DISTINCT COALESCE(NULLIF(pg_tablespace_location(b.oid),'')||suffix, 'base/pgsql_tmp') AS dir
> +FROM pg_tablespace b, pg_control_system() pcs,
> +LATERAL format('/PG_%s_%s', left(current_setting('server_version_num'), 2), pcs.catalog_version_no) AS suffix) AS dir,
> +LATERAL pg_ls_dir_recurse(dir) AS a;
> 
> For context, the line of reasoning that led me to this patch series was
> something like this:
> 
> 0) Why can't I list shared tempfiles (dirs) using pg_ls_tmpdir() ?
> 1) Implement recursion for pg_ls_tmpdir();
> 2) Eventually realize that it's silly to implement a function to recurse into
>    one particular directory when no general feature exists;
> 3) Implement generic facility;

On Tue, Apr 06, 2021 at 11:01:31AM -0500, Justin Pryzby wrote:
> The first handful of patches address the original issue, and I think could be
> "ready":
> 
> $ git log --oneline origin..pg-ls-dir-new |tac
> ... Document historic behavior of links to directories..
> ... Add tests on pg_ls_dir before changing it
> ... Add pg_ls_dir_metadata to list a dir with file metadata..
> ... pg_ls_tmpdir to show directories and "isdir" argument..
> ... pg_ls_*dir to show directories and "isdir" column..
> 
> These others are optional:
> ... pg_ls_logdir to ignore error if initial/top dir is missing..
> ... pg_ls_*dir to return all the metadata from pg_stat_file..
> 
> ..and these maybe requires more work for lstat on windows:
> ... pg_stat_file and pg_ls_dir_* to use lstat()..
> ... pg_ls_*/pg_stat_file to show file *type*..
> ... Preserve pg_stat_file() isdir..
> ... Add recursion option in pg_ls_dir_files..

On Tue, Jan 25, 2022 at 01:27:55PM -0600, Justin Pryzby wrote:
> The original motive for the patch was that pg_ls_tmpdir doesn't show shared
> filesets.

Attachments:

  [text/x-diff] v36-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20220624043549.GC22452@telsasoft.com/2-v36-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From 772c1300d7b8c08a12f7968bc21c65b7cfd252a8 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v36 1/7] Document historic behavior of links to directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b652460a18..933118203f4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28661,6 +28661,10 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         platforms only), file creation time stamp (Windows only), and a flag
         indicating if it is a directory.
        </para>
+       <para>
+       If filename is a link, this function returns information about the file
+       or directory the link refers to.
+       </para>
        <para>
         This function is restricted to superusers by default, but other users
         can be granted EXECUTE to run the function.
-- 
2.17.1

  [text/x-diff] v36-0002-Add-tests-before-changing-pg_ls_.patch (3.4K, ../../20220624043549.GC22452@telsasoft.com/3-v36-0002-Add-tests-before-changing-pg_ls_.patch)
  download | inline diff:
From f28fd855596fdc1623679d2b25b6fa39d39e1e61 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v36 2/7] Add tests before changing pg_ls_*

---
 src/test/regress/expected/misc_functions.out | 59 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      | 15 +++++
 2 files changed, 74 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 01d1ad0b9a4..45544469af5 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -416,6 +416,65 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalmapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalsnapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_replslotdir('') limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_tmpdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_waldir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 072fc36a1ff..59d6e517503 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -140,6 +140,21 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+select * from pg_ls_logdir() limit 0;
+select * from pg_ls_logicalmapdir() limit 0;
+select * from pg_ls_logicalsnapdir() limit 0;
+select * from pg_ls_replslotdir('') limit 0;
+select * from pg_ls_tmpdir() limit 0;
+select * from pg_ls_waldir() limit 0;
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.17.1

  [text/x-diff] v36-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (17.8K, ../../20220624043549.GC22452@telsasoft.com/4-v36-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 423f5ff33853805e29446c2c124d2ce3a3167c83 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v36 3/7] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 205 ++++++++++++-------
 src/include/catalog/pg_proc.dat              |  12 ++
 src/test/regress/expected/misc_functions.out |  24 +++
 src/test/regress/expected/tablespace.out     |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 src/test/regress/sql/tablespace.sql          |   5 +
 8 files changed, 217 insertions(+), 70 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 933118203f4..ba30faa8441 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25940,6 +25940,27 @@ SELECT collation for ('foo' COLLATE "de_DE");
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 73da687d5dd..3fa6f9af0a8 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -700,6 +700,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 2bf52192567..c7322ed405a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything beginning with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -446,6 +461,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -473,54 +493,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	DIR		   *dirdesc;
-	struct dirent *de;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
-							 values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -533,23 +508,55 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	DIR		   *dirdesc;
 	struct dirent *de;
 
-	SetSingleFuncCall(fcinfo, 0);
+	/* isdir depends on metadata */
+	Assert(!(flags & LS_DIR_ISDIR) || (flags & LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags & LS_DIR_ISDIR) || !(flags & LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		/* missing_ok */
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		/* include_dot_dirs */
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
+
+	if (flags & LS_DIR_METADATA)
+		SetSingleFuncCall(fcinfo, 0);
+	else
+		SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
 
 	/*
 	 * Now walk the directory.  Note that we must do this within a single SRF
@@ -560,20 +567,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -588,13 +602,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+				else
+#endif
+					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -608,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -633,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -662,7 +699,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
 
 /*
@@ -671,7 +736,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
 }
 
 /*
@@ -680,7 +745,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
 }
 
 /*
@@ -705,5 +770,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, false);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a331..0e5c977e69f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11815,6 +11815,18 @@
   proargmodes => '{i,o,o,o}',
   proargnames => '{slot_name,name,size,modification}',
   prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 45544469af5..e54e38f54ad 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -475,6 +475,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf9..8159c9f18f1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -33,6 +33,14 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
  pg_tblspc/NNN
 (1 row)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 59d6e517503..fcb456f434d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -155,6 +155,17 @@ select * from pg_ls_tmpdir() limit 0;
 select * from pg_ls_waldir() limit 0;
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a8..cf683c3bf3a 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -27,6 +27,11 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN')
   FROM pg_tablespace  WHERE spcname = 'regress_tblspace';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v36-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.6K, ../../20220624043549.GC22452@telsasoft.com/5-v36-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From a162df02f5763cb9c71c1d52d689763dba939f61 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v36 4/7] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  8 ++++----
 src/test/regress/expected/tablespace.out     |  4 ++--
 5 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ba30faa8441..8767ef0d7a2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28591,16 +28591,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c7322ed405a..eb74130e721 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -670,7 +670,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0e5c977e69f..381eb0e277f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11785,13 +11785,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '6270',
   descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index e54e38f54ad..77bf661627b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -461,8 +461,8 @@ select * from pg_ls_replslotdir('') limit 0;
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
@@ -478,8 +478,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 8159c9f18f1..ce5d6f73e01 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,8 +37,8 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

  [text/x-diff] v36-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (13.2K, ../../20220624043549.GC22452@telsasoft.com/6-v36-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 5f01e31df8a2e97205453a4fb648f5c6a87b6345 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v36 5/7] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir, ...

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 21 +++++-------
 src/include/catalog/pg_proc.dat              | 26 +++++++-------
 src/test/regress/expected/misc_functions.out | 28 +++++++--------
 4 files changed, 57 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8767ef0d7a2..8e724b4474f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25951,7 +25951,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -28446,12 +28447,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and roles with privileges of
@@ -28469,13 +28472,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and roles with privileges of
@@ -28566,13 +28570,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index eb74130e721..fd837e22987 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -47,11 +47,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -644,14 +641,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -670,7 +667,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -700,7 +697,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -736,7 +733,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_COMMON);
 }
 
 /*
@@ -745,7 +742,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_COMMON);
 }
 
 /*
@@ -770,5 +767,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_COMMON);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 381eb0e277f..ec989867404 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11769,18 +11769,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -11797,23 +11797,23 @@
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '6271',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '6272',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{slot_name,name,size,modification}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{slot_name,name,size,modification,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 77bf661627b..94b5eac47ea 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -436,28 +436,28 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
@@ -466,8 +466,8 @@ select * from pg_ls_tmpdir() limit 0;
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
-- 
2.17.1

  [text/x-diff] v36-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20220624043549.GC22452@telsasoft.com/7-v36-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 8774013e1998037fa21f24f2769d86de692c4797 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v36 6/7] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fd837e22987..a01c7109f4c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -641,7 +641,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index ce5d6f73e01..53662fb0efd 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -41,6 +41,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index cf683c3bf3a..18b435c9abb 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -32,6 +32,10 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1

  [text/x-diff] v36-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (21.6K, ../../20220624043549.GC22452@telsasoft.com/8-v36-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 49f1da8138610033a10bb12db38074e25e19e4f4 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v36 7/7] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 ++++++---
 src/backend/utils/adt/genfile.c              | 72 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 42 ++++++------
 src/test/regress/expected/misc_functions.out | 40 +++++------
 src/test/regress/expected/tablespace.out     |  8 +--
 5 files changed, 102 insertions(+), 94 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8e724b4474f..fdc905c523d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25951,7 +25951,10 @@ SELECT collation for ('foo' COLLATE "de_DE");
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -28447,13 +28450,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -28472,13 +28478,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -28570,14 +28579,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -28597,13 +28609,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a01c7109f4c..a51cf29f095 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -394,6 +396,28 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
 	return pg_read_binary_file(fcinfo);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -404,7 +428,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -444,27 +468,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -571,8 +577,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -611,24 +617,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-				else
-#endif
-					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ec989867404..bbddb34ff6a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11769,63 +11769,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '6270',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '6271',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '6272',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargmodes => '{i,o,o,o,o}',
-  proargnames => '{slot_name,name,size,modification,isdir}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 94b5eac47ea..afb1744bee7 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -436,38 +436,38 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
@@ -478,8 +478,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -495,8 +495,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 53662fb0efd..ef64e7bfa8b 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,15 +37,15 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.17.1

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-06-24 04:35                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2022-10-28 00:38                                                                   ` Justin Pryzby <pryzby@telsasoft.com>
  2024-01-22 01:16                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Peter Smith <smithpb2250@gmail.com>
  0 siblings, 1 reply; 70+ messages in thread

From: Justin Pryzby @ 2022-10-28 00:38 UTC (permalink / raw)
  To: Fabien COELHO <coelho@cri.ensmp.fr>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; +Cc: pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>

On Fri, Dec 13, 2019 at 03:03:47PM +1300, Thomas Munro wrote:
> > Actually, I tried using pg_ls_tmpdir(), but it unconditionally masks
> > non-regular files and thus shared filesets.  Maybe that's worth
> > discussion on a new thread ?
> >
> > src/backend/utils/adt/genfile.c
> >                 /* Ignore anything but regular files */
> >                 if (!S_ISREG(attrib.st_mode))
> >                         continue;
> 
> +1, that's worth fixing.

@cfbot: rebased on eddc128be.

Attachments:

  [text/x-diff] v37-0001-Document-historic-behavior-of-links-to-directori.patch (1.0K, ../../20221028003815.GG16921@telsasoft.com/2-v37-0001-Document-historic-behavior-of-links-to-directori.patch)
  download | inline diff:
From f641f63a1ff3efd90a3831927c758636cc82d080 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 16 Mar 2020 14:12:55 -0500
Subject: [PATCH v37 01/11] Document historic behavior of links to
 directories..

Backpatch to 9.5: pg_stat_file
---
 doc/src/sgml/func.sgml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6e0425cb3dc..d958c3e74ac 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27700,6 +27700,10 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         platforms only), file creation time stamp (Windows only), and a flag
         indicating if it is a directory.
        </para>
+       <para>
+       If filename is a link, this function returns information about the file
+       or directory the link refers to.
+       </para>
        <para>
         This function is restricted to superusers by default, but other users
         can be granted EXECUTE to run the function.
-- 
2.25.1

  [text/x-diff] v37-0002-Add-tests-before-changing-pg_ls_.patch (3.4K, ../../20221028003815.GG16921@telsasoft.com/3-v37-0002-Add-tests-before-changing-pg_ls_.patch)
  download | inline diff:
From 3f28fa01fa8e01b633e5c2a24ba4deed4abe6053 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Tue, 17 Mar 2020 13:16:24 -0500
Subject: [PATCH v37 02/11] Add tests before changing pg_ls_*

---
 src/test/regress/expected/misc_functions.out | 59 ++++++++++++++++++++
 src/test/regress/sql/misc_functions.sql      | 15 +++++
 2 files changed, 74 insertions(+)

diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 88bb696ded8..77d285ecc85 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -480,6 +480,65 @@ select count(*) > 0 from
  t
 (1 row)
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+ name 
+------
+ .
+(1 row)
+
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+ name 
+------
+(0 rows)
+
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+ pg_ls_dir 
+-----------
+(0 rows)
+
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+ERROR:  could not open directory "does not exist": No such file or directory
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalmapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_logicalsnapdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_replslotdir('') limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_tmpdir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_ls_waldir() limit 0;
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select * from pg_stat_file('.') limit 0;
+ size | access | modification | change | creation | isdir 
+------+--------+--------------+--------+----------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b07e9e8dbb3..d299f3d8949 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -166,6 +166,21 @@ select count(*) > 0 from
    where spcname = 'pg_default') pts
   join pg_database db on pts.pts = db.oid;
 
+select * from (select pg_ls_dir('.', false, true) as name) as ls where ls.name='.'; -- include_dot_dirs=true
+select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name='.'; -- include_dot_dirs=false
+select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
+select pg_ls_dir('does not exist'); -- fails with missingok=false
+
+-- Check that expected columns are present
+select * from pg_ls_archive_statusdir() limit 0;
+select * from pg_ls_logdir() limit 0;
+select * from pg_ls_logicalmapdir() limit 0;
+select * from pg_ls_logicalsnapdir() limit 0;
+select * from pg_ls_replslotdir('') limit 0;
+select * from pg_ls_tmpdir() limit 0;
+select * from pg_ls_waldir() limit 0;
+select * from pg_stat_file('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
-- 
2.25.1

  [text/x-diff] v37-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch (17.8K, ../../20221028003815.GG16921@telsasoft.com/4-v37-0003-Add-pg_ls_dir_metadata-to-list-a-dir-with-file-m.patch)
  download | inline diff:
From 2fcff37d19869c83eab7c8116946854f52a20515 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 22:40:24 -0500
Subject: [PATCH v37 03/11] Add pg_ls_dir_metadata to list a dir with file
 metadata..

Generalize pg_ls_dir_files and retire pg_ls_dir

Need catversion bumped?
---
 doc/src/sgml/func.sgml                       |  21 ++
 src/backend/catalog/system_functions.sql     |   1 +
 src/backend/utils/adt/genfile.c              | 205 ++++++++++++-------
 src/include/catalog/pg_proc.dat              |  12 ++
 src/test/regress/expected/misc_functions.out |  24 +++
 src/test/regress/expected/tablespace.out     |   8 +
 src/test/regress/sql/misc_functions.sql      |  11 +
 src/test/regress/sql/tablespace.sql          |   5 +
 8 files changed, 217 insertions(+), 70 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d958c3e74ac..a80d794a43a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25900,6 +25900,27 @@ LOG:  Grand total: 1651920 bytes in 201 blocks; 622360 free (88 chunks); 1029560
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_ls_dir_metadata</primary>
+        </indexterm>
+        <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+        <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+        <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+        <returnvalue>setof record</returnvalue>
+        ( <parameter>filename</parameter> <type>text</type>,
+        <parameter>size</parameter> <type>bigint</type>,
+        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+       </para>
+       <para>
+        For each file in the specified directory, list the file and its
+        metadata.
+        Restricted to superusers by default, but other users can be granted
+        EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 30a048f6b09..b77b83017cb 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -704,6 +704,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
 
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ab6f67f8747..c041c7630c8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
+#define	LS_DIR_METADATA				(1<<1) /* Show columns: mtime, size */
+#define	LS_DIR_MISSING_OK			(1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define	LS_DIR_SKIP_DOT_DIRS		(1<<3) /* Do not show . or .. */
+#define	LS_DIR_SKIP_HIDDEN			(1<<4) /* Do not show anything beginning with . */
+#define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
+#define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -516,6 +531,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
 #endif
 	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+	/* Links should have isdir=false */
+	if (pgwin32_is_junction(filename))
+		values[5] = BoolGetDatum(false);
+#endif
 
 	tuple = heap_form_tuple(tupdesc, values, isnull);
 
@@ -543,54 +563,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir(PG_FUNCTION_ARGS)
 {
-	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-	char	   *location;
-	bool		missing_ok = false;
-	bool		include_dot_dirs = false;
-	DIR		   *dirdesc;
-	struct dirent *de;
-
-	location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
-	/* check the optional arguments */
-	if (PG_NARGS() == 3)
-	{
-		if (!PG_ARGISNULL(1))
-			missing_ok = PG_GETARG_BOOL(1);
-		if (!PG_ARGISNULL(2))
-			include_dot_dirs = PG_GETARG_BOOL(2);
-	}
-
-	InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
-
-	dirdesc = AllocateDir(location);
-	if (!dirdesc)
-	{
-		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
-			return (Datum) 0;
-		/* Otherwise, we can let ReadDir() throw the error */
-	}
-
-	while ((de = ReadDir(dirdesc, location)) != NULL)
-	{
-		Datum		values[1];
-		bool		nulls[1];
-
-		if (!include_dot_dirs &&
-			(strcmp(de->d_name, ".") == 0 ||
-			 strcmp(de->d_name, "..") == 0))
-			continue;
-
-		values[0] = CStringGetTextDatum(de->d_name);
-		nulls[0] = false;
-
-		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
-							 values, nulls);
-	}
-
-	FreeDir(dirdesc);
-	return (Datum) 0;
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
@@ -603,23 +578,55 @@ pg_ls_dir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_dir_1arg(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir(fcinfo);
+	text	*filename_t = PG_GETARG_TEXT_PP(0);
+	char	*filename = convert_and_check_filename(filename_t);
+	return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
 }
 
 /*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
  *
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
  * Other unreadable-directory cases throw an error.
  */
 static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 {
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	DIR		   *dirdesc;
 	struct dirent *de;
 
-	InitMaterializedSRF(fcinfo, 0);
+	/* isdir depends on metadata */
+	Assert(!(flags & LS_DIR_ISDIR) || (flags & LS_DIR_METADATA));
+	/* Unreasonable to show isdir and skip dirs */
+	Assert(!(flags & LS_DIR_ISDIR) || !(flags & LS_DIR_SKIP_DIRS));
+
+	/* check the optional arguments */
+	if (PG_NARGS() == 3)
+	{
+		/* missing_ok */
+		if (!PG_ARGISNULL(1))
+		{
+			if (PG_GETARG_BOOL(1))
+				flags |= LS_DIR_MISSING_OK;
+			else
+				flags &= ~LS_DIR_MISSING_OK;
+		}
+
+		/* include_dot_dirs */
+		if (!PG_ARGISNULL(2))
+		{
+			if (PG_GETARG_BOOL(2))
+				flags &= ~LS_DIR_SKIP_DOT_DIRS;
+			else
+				flags |= LS_DIR_SKIP_DOT_DIRS;
+		}
+	}
+
+	if (flags & LS_DIR_METADATA)
+		InitMaterializedSRF(fcinfo, 0);
+	else
+		InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
 
 	/*
 	 * Now walk the directory.  Note that we must do this within a single SRF
@@ -630,20 +637,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 	if (!dirdesc)
 	{
 		/* Return empty tuplestore if appropriate */
-		if (missing_ok && errno == ENOENT)
+		if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
 			return (Datum) 0;
 		/* Otherwise, we can let ReadDir() throw the error */
 	}
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[3];
-		bool		nulls[3];
+		Datum		values[4];
+		bool		nulls[4];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
-		/* Skip hidden files */
-		if (de->d_name[0] == '.')
+		/* Skip dot dirs? */
+		if (flags & LS_DIR_SKIP_DOT_DIRS &&
+			(strcmp(de->d_name, ".") == 0 ||
+			 strcmp(de->d_name, "..") == 0))
+			continue;
+
+		/* Skip hidden files? */
+		if (flags & LS_DIR_SKIP_HIDDEN &&
+			de->d_name[0] == '.')
 			continue;
 
 		/* Get the file info */
@@ -658,13 +672,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 					 errmsg("could not stat file \"%s\": %m", path)));
 		}
 
-		/* Ignore anything but regular files */
-		if (!S_ISREG(attrib.st_mode))
-			continue;
+		/* Skip dirs or special files? */
+		if (S_ISDIR(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_DIRS)
+				continue;
+		}
+		else if (!S_ISREG(attrib.st_mode))
+		{
+			if (flags & LS_DIR_SKIP_SPECIAL)
+				continue;
+		}
 
 		values[0] = CStringGetTextDatum(de->d_name);
-		values[1] = Int64GetDatum((int64) attrib.st_size);
-		values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+		if (flags & LS_DIR_METADATA)
+		{
+			values[1] = Int64GetDatum((int64) attrib.st_size);
+			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+			if (flags & LS_DIR_ISDIR)
+			{
+#ifdef WIN32
+				/* Links should have isdir=false */
+				if (pgwin32_is_junction(path))
+					values[3] = BoolGetDatum(false);
+				else
+#endif
+					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+			}
+		}
+
 		memset(nulls, 0, sizeof(nulls));
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -678,14 +714,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, false);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
 }
 
 /*
@@ -703,7 +739,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 						tblspc)));
 
 	TempTablespacePath(path, tblspc);
-	return pg_ls_dir_files(fcinfo, path, true);
+	return pg_ls_dir_files(fcinfo, path,
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -732,7 +769,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+	char	*dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+	return pg_ls_dir_files(fcinfo, dirname,
+			LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
 }
 
 /*
@@ -741,7 +806,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
 }
 
 /*
@@ -750,7 +815,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
 }
 
 /*
@@ -775,5 +840,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, false);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 20f5aa56eab..89892647808 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11758,6 +11758,18 @@
   proargmodes => '{i,o,o,o}',
   proargnames => '{slot_name,name,size,modification}',
   prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+  proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{dirname,filename,size,modification,isdir}',
+  prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
 { oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 77d285ecc85..a656f9ad07e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -539,6 +539,30 @@ select * from pg_stat_file('.') limit 0;
 ------+--------+--------------+--------+----------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir 
+----------+-------
+ .        | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir 
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir 
+----------+------+--------------+-------
+(0 rows)
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf9..8159c9f18f1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -33,6 +33,14 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
  pg_tblspc/NNN
 (1 row)
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index d299f3d8949..f808555382d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -181,6 +181,17 @@ select * from pg_ls_tmpdir() limit 0;
 select * from pg_ls_waldir() limit 0;
 select * from pg_stat_file('.') limit 0;
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
 --
 -- Test replication slot directory functions
 --
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a8..cf683c3bf3a 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -27,6 +27,11 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN')
   FROM pg_tablespace  WHERE spcname = 'regress_tblspace';
 
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.25.1

  [text/x-diff] v37-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch (6.6K, ../../20221028003815.GG16921@telsasoft.com/5-v37-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch)
  download | inline diff:
From 2e2fd8b0389af5d3c5e617cbe5093bd8686c329e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Sun, 8 Mar 2020 22:57:54 -0500
Subject: [PATCH v37 04/11] pg_ls_tmpdir to show directories and "isdir"
 argument..

similar to pg_stat_file().

It's worth breaking the function's return type, since core postgres creates
"shared filesets" underneath the temp dirs, and it's unreasonable to not show
them here, and the alternative query to show them is unreasaonably complicated.

See following commit which also adds these columns to the other pg_ls_*
functions.  Although I don't think it matters that they're easily UNIONed, it'd
still make great sense if they returned the same columns.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 17 +++++++++--------
 src/backend/utils/adt/genfile.c              |  2 +-
 src/include/catalog/pg_proc.dat              |  8 ++++----
 src/test/regress/expected/misc_functions.out |  8 ++++----
 src/test/regress/expected/tablespace.out     |  4 ++--
 5 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a80d794a43a..cff999032bf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -27630,16 +27630,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the temporary file directory for the
-        specified <parameter>tablespace</parameter>.
-        If <parameter>tablespace</parameter> is not provided,
-        the <literal>pg_default</literal> tablespace is examined.  Filenames
-        beginning with a dot, directories, and other special files are
-        excluded.
+        For each file in the temporary directory within the given
+        <parameter>tablespace</parameter>, return the file's name, size, last
+        modification time (mtime) and a boolean indicating if the file is a directory.
+        Directories are used for temporary files shared by parallel processes.
+        If <parameter>tablespace</parameter> is not provided, the
+        <literal>pg_default</literal> tablespace is examined.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c041c7630c8..5d374177a04 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -740,7 +740,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 89892647808..e2f3361bf37 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11728,13 +11728,13 @@
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
-  proargnames => '{tablespace,name,size,modification}',
+  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+  proargnames => '{tablespace,name,size,modification,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '6270',
   descr => 'list of files in the pg_logical/snapshots directory',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index a656f9ad07e..bc77ad62958 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -525,8 +525,8 @@ select * from pg_ls_replslotdir('') limit 0;
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
@@ -542,8 +542,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 8159c9f18f1..ce5d6f73e01 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,8 +37,8 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.25.1

  [text/x-diff] v37-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch (13.3K, ../../20221028003815.GG16921@telsasoft.com/6-v37-0005-pg_ls_-dir-to-show-directories-and-isdir-column.patch)
  download | inline diff:
From 6a1d1cbfdb56024092e5039d22dbb1a24434dcae Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 01:00:42 -0500
Subject: [PATCH v37 05/11] pg_ls_*dir to show directories and "isdir" column..

pg_ls_logdir, pg_ls_waldir, pg_ls_archive_statusdir, ...

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 36 ++++++++++++--------
 src/backend/utils/adt/genfile.c              | 21 +++++-------
 src/include/catalog/pg_proc.dat              | 26 +++++++-------
 src/test/regress/expected/misc_functions.out | 28 +++++++--------
 4 files changed, 57 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index cff999032bf..3a4379058dd 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25911,7 +25911,8 @@ LOG:  Grand total: 1651920 bytes in 201 blocks; 622360 free (88 chunks); 1029560
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the specified directory, list the file and its
@@ -27485,12 +27486,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's log directory.  Filenames beginning with
-        a dot, directories, and other special files are excluded.
+        For each file in the server's log directory,
+        return the file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and roles with privileges of
@@ -27508,13 +27511,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's write-ahead log (WAL) directory.
-        Filenames beginning with a dot, directories, and other special files
-        are excluded.
+        For each file in the server's write-ahead log (WAL) directory, list the
+        file's name, size, last modification time (mtime), and a boolean
+        indicating if the file is a directory.
+        Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
         This function is restricted to superusers and roles with privileges of
@@ -27605,13 +27609,15 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
-        <parameter>modification</parameter> <type>timestamp with time zone</type> )
+        <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
-        Returns the name, size, and last modification time (mtime) of each
-        ordinary file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>).  Filenames beginning
-        with a dot, directories, and other special files are excluded.
+        For each file in the server's WAL archive status directory
+        (<filename>pg_wal/archive_status</filename>), list the file's
+        name, size, last modification time (mtime), and a boolean indicating if
+        the file is a directory.
+        Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
         This function is restricted to superusers and members of
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5d374177a04..5356136ff5a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -47,11 +47,8 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
 #define	LS_DIR_SKIP_DIRS			(1<<5) /* Do not show directories */
 #define	LS_DIR_SKIP_SPECIAL			(1<<6) /* Do not show special file types */
 
-/*
- * Shortcut for the historic behavior of the pg_ls_* functions (not including
- * pg_ls_dir, which skips different files and doesn't show metadata).
- */
-#define LS_DIR_HISTORIC				(LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+/* Shortcut for common behavior */
+#define LS_DIR_COMMON				(LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
 
 /*
  * Convert a "text" filename argument to C string, and check it's allowable.
@@ -714,14 +711,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
 }
 
 /* Function to return the list of files in the WAL directory */
 Datum
 pg_ls_waldir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_COMMON);
 }
 
 /*
@@ -740,7 +737,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
 
 	TempTablespacePath(path, tblspc);
 	return pg_ls_dir_files(fcinfo, path,
-			LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR | LS_DIR_METADATA | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -770,7 +767,7 @@ Datum
 pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
-			LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+			LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /*
@@ -806,7 +803,7 @@ pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_COMMON);
 }
 
 /*
@@ -815,7 +812,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
 Datum
 pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_COMMON);
 }
 
 /*
@@ -840,5 +837,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
 						slotname)));
 
 	snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
-	return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
+	return pg_ls_dir_files(fcinfo, path, LS_DIR_COMMON);
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e2f3361bf37..3addb31e45b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11712,18 +11712,18 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz}', proargmodes => '{o,o,o}',
-  proargnames => '{name,size,modification}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
+  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
@@ -11740,23 +11740,23 @@
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '6271',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz}',
-  proargmodes => '{o,o,o}', proargnames => '{name,size,modification}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
+  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '6272',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz}',
-  proargmodes => '{i,o,o,o}',
-  proargnames => '{slot_name,name,size,modification}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o}',
+  proargnames => '{slot_name,name,size,modification,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index bc77ad62958..4605fede3f2 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -500,28 +500,28 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
@@ -530,8 +530,8 @@ select * from pg_ls_tmpdir() limit 0;
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification 
-------+------+--------------
+ name | size | modification | isdir 
+------+------+--------------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
-- 
2.25.1

  [text/x-diff] v37-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch (3.1K, ../../20221028003815.GG16921@telsasoft.com/7-v37-0006-pg_ls_logdir-to-ignore-error-if-initial-top-dir-.patch)
  download | inline diff:
From 210cab7947ab4eea52725ac7b8a5fcc62dcb1603 Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Fri, 6 Mar 2020 17:23:51 -0600
Subject: [PATCH v37 06/11] pg_ls_logdir to ignore error if initial/top dir is
 missing..

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5356136ff5a..5402ecb3d05 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -711,7 +711,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index ce5d6f73e01..53662fb0efd 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -41,6 +41,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index cf683c3bf3a..18b435c9abb 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -32,6 +32,10 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.25.1

  [text/x-diff] v37-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch (21.6K, ../../20221028003815.GG16921@telsasoft.com/8-v37-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch)
  download | inline diff:
From 5bdc6200147c7d1545ec7578062f3530a511602e Mon Sep 17 00:00:00 2001
From: Justin Pryzby <pryzbyj@telsasoft.com>
Date: Mon, 9 Mar 2020 21:56:21 -0500
Subject: [PATCH v37 07/11] pg_ls_*dir to return all the metadata from
 pg_stat_file..

..but it doesn't seem worth factoring out the common bits, since stat_file
doesn't return a name, so all the field numbers are off by one.

NOTE, the atime is now shown where the mtime used to be.

Need catversion bump
---
 doc/src/sgml/func.sgml                       | 34 ++++++---
 src/backend/utils/adt/genfile.c              | 72 +++++++++-----------
 src/include/catalog/pg_proc.dat              | 42 ++++++------
 src/test/regress/expected/misc_functions.out | 40 +++++------
 src/test/regress/expected/tablespace.out     |  8 +--
 5 files changed, 102 insertions(+), 94 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3a4379058dd..59bbcb796a6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25911,7 +25911,10 @@ LOG:  Grand total: 1651920 bytes in 201 blocks; 622360 free (88 chunks); 1029560
         <returnvalue>setof record</returnvalue>
         ( <parameter>filename</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
@@ -27486,13 +27489,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's log directory,
-        return the file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        return the file's name, along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27511,13 +27517,16 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's write-ahead log (WAL) directory, list the
-        file's name, size, last modification time (mtime), and a boolean
-        indicating if the file is a directory.
+        file's name along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special files types are excluded.
        </para>
        <para>
@@ -27609,14 +27618,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the server's WAL archive status directory
-        (<filename>pg_wal/archive_status</filename>), list the file's
-        name, size, last modification time (mtime), and a boolean indicating if
-        the file is a directory.
+        (<filename>pg_wal/archive_status</filename>), list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Filenames beginning with a dot and special file types are excluded.
        </para>
        <para>
@@ -27636,13 +27648,17 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         <returnvalue>setof record</returnvalue>
         ( <parameter>name</parameter> <type>text</type>,
         <parameter>size</parameter> <type>bigint</type>,
+        <parameter>access</parameter> <type>timestamp with time zone</type>,
         <parameter>modification</parameter> <type>timestamp with time zone</type>,
+        <parameter>change</parameter> <type>timestamp with time zone</type>,
+        <parameter>creation</parameter> <type>timestamp with time zone</type>,
         <parameter>isdir</parameter> <type>boolean</type> )
        </para>
        <para>
         For each file in the temporary directory within the given
-        <parameter>tablespace</parameter>, return the file's name, size, last
-        modification time (mtime) and a boolean indicating if the file is a directory.
+        <parameter>tablespace</parameter>, list the file's name
+        along with the metadata columns returned by
+        <function>pg_stat_file</function>.
         Directories are used for temporary files shared by parallel processes.
         If <parameter>tablespace</parameter> is not provided, the
         <literal>pg_default</literal> tablespace is examined.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5402ecb3d05..ce4a37a3301 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,8 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+static void values_from_stat(struct stat *fst, const char *path, Datum *values,
+		bool *nulls);
 static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
 
 #define	LS_DIR_ISDIR				(1<<0) /* Show column: isdir */
@@ -464,6 +466,28 @@ pg_read_binary_file_all_missing(PG_FUNCTION_ARGS)
 	PG_RETURN_BYTEA_P(ret);
 }
 
+/*
+ * Populate values and nulls from fst and path.
+ * Used for pg_stat_file() and pg_ls_dir_files()
+ * nulls is assumed to have been zerod.
+ */
+static void
+values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
+{
+	values[0] = Int64GetDatum((int64) fst->st_size);
+	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_atime));
+	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_mtime));
+	/* Unix has file status change time, while Win32 has creation time */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+	nulls[4] = true;
+#else
+	nulls[3] = true;
+	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
+#endif
+	values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+}
+
 /*
  * stat a file
  */
@@ -474,7 +498,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
 	char	   *filename;
 	struct stat fst;
 	Datum		values[6];
-	bool		isnull[6];
+	bool		nulls[6];
 	HeapTuple	tuple;
 	TupleDesc	tupdesc;
 	bool		missing_ok = false;
@@ -514,27 +538,9 @@ pg_stat_file(PG_FUNCTION_ARGS)
 					   "isdir", BOOLOID, -1, 0);
 	BlessTupleDesc(tupdesc);
 
-	memset(isnull, false, sizeof(isnull));
-
-	values[0] = Int64GetDatum((int64) fst.st_size);
-	values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime));
-	values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime));
-	/* Unix has file status change time, while Win32 has creation time */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-	values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-	isnull[4] = true;
-#else
-	isnull[3] = true;
-	values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
-#endif
-	values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
-#ifdef WIN32
-	/* Links should have isdir=false */
-	if (pgwin32_is_junction(filename))
-		values[5] = BoolGetDatum(false);
-#endif
-
-	tuple = heap_form_tuple(tupdesc, values, isnull);
+	memset(nulls, false, sizeof(nulls));
+	values_from_stat(&fst, filename, values, nulls);
+	tuple = heap_form_tuple(tupdesc, values, nulls);
 
 	pfree(filename);
 
@@ -641,8 +647,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 
 	while ((de = ReadDir(dirdesc, dir)) != NULL)
 	{
-		Datum		values[4];
-		bool		nulls[4];
+		Datum		values[7];
+		bool		nulls[7];
 		char		path[MAXPGPATH * 2];
 		struct stat attrib;
 
@@ -681,24 +687,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 				continue;
 		}
 
+		memset(nulls, false, sizeof(nulls));
 		values[0] = CStringGetTextDatum(de->d_name);
 		if (flags & LS_DIR_METADATA)
-		{
-			values[1] = Int64GetDatum((int64) attrib.st_size);
-			values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
-			if (flags & LS_DIR_ISDIR)
-			{
-#ifdef WIN32
-				/* Links should have isdir=false */
-				if (pgwin32_is_junction(path))
-					values[3] = BoolGetDatum(false);
-				else
-#endif
-					values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
-			}
-		}
-
-		memset(nulls, 0, sizeof(nulls));
+			values_from_stat(&attrib, path, 1+values, 1+nulls);
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
 	}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3addb31e45b..29eec1e9045 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11712,63 +11712,63 @@
 { oid => '3353', descr => 'list files in the log directory',
   proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_logdir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
 { oid => '3354', descr => 'list of files in the WAL directory',
   proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_waldir' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
 { oid => '5031', descr => 'list of files in the archive_status directory',
   proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_archive_statusdir' },
 { oid => '5029', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{text,int8,timestamptz,bool}', proargmodes => '{o,o,o,o}',
-  proargnames => '{name,size,modification,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+  proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
 { oid => '5030', descr => 'list files in the pgsql_tmp directory',
   proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
-  proallargtypes => '{oid,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{tablespace,name,size,modification,isdir}',
+  proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_tmpdir_1arg' },
 { oid => '6270',
   descr => 'list of files in the pg_logical/snapshots directory',
   proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalsnapdir' },
 { oid => '6271',
   descr => 'list of files in the pg_logical/mappings directory',
   proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => '', proallargtypes => '{text,int8,timestamptz,bool}',
-  proargmodes => '{o,o,o,o}', proargnames => '{name,size,modification,isdir}',
+  proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_logicalmapdir' },
 { oid => '6272',
   descr => 'list of files in the pg_replslot/slot_name directory',
   proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
   proretset => 't', provolatile => 'v', prorettype => 'record',
-  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,bool}',
-  proargmodes => '{i,o,o,o,o}',
-  proargnames => '{slot_name,name,size,modification,isdir}',
+  proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_replslotdir' },
 { oid => '8450', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
-  proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
-  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+  proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata' },
 { oid => '8451', descr => 'list directory with metadata',
   proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => 'text',
-  proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
-  proargnames => '{dirname,filename,size,modification,isdir}',
+  proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
+  proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
   prosrc => 'pg_ls_dir_metadata_1arg' },
 
 # hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 4605fede3f2..fd67508e455 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -349,8 +349,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
 
 -- Test not-run-to-completion cases.
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -500,38 +500,38 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
 ERROR:  could not open directory "does not exist": No such file or directory
 -- Check that expected columns are present
 select * from pg_ls_archive_statusdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalmapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_logicalsnapdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_replslotdir('') limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_tmpdir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_ls_waldir() limit 0;
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select * from pg_stat_file('.') limit 0;
@@ -542,8 +542,8 @@ select * from pg_stat_file('.') limit 0;
 -- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 select * from pg_ls_tmpdir() where name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
@@ -559,8 +559,8 @@ select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename
 
 -- Check that expected columns are present
 select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | modification | isdir 
-----------+------+--------------+-------
+ filename | size | access | modification | change | creation | isdir 
+----------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 --
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 53662fb0efd..ef64e7bfa8b 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -37,15 +37,15 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
 -- The name='' condition is never true, so the function runs to completion but returns zero rows.
 SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | modification | isdir 
-------+------+--------------+-------
+ name | size | access | modification | change | creation | isdir 
+------+------+--------+--------------+--------+----------+-------
 (0 rows)
 
 -- try setting and resetting some properties for the new tablespace
-- 
2.25.1

^ permalink  raw  reply  [nested|flat] 70+ messages in thread

* Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*)
  2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-06 23:35 ` Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
  2020-03-07 14:14   ` Re: pg_ls_tmpdir to show directories and shared filesets Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
  2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
  2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
  2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
  2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-06-24 04:35                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
  2022-10-28 00:38                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
@ 2024-01-22 01:16                                                                     ` Peter Smith <smithpb2250@gmail.com>
  0 siblings, 0 replies; 70+ messages in thread

From: Peter Smith @ 2024-01-22 01:16 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Fabien COELHO <coelho@cri.ensmp.fr>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers@postgresql.org, "Bossart, Nathan" <bossartn@amazon.com>; Thomas Munro <thomas.munro@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; David Steele <david@pgmasters.net>

2024-01 Commitfest.

Hi, this patch was marked in CF as "Needs Review", but there has been
no activity on this thread for 14+ months.

Since there seems not much interest, I have changed the status to
"Returned with Feedback" [1]. Feel free to propose a stronger use case
for the patch and add an entry for the same.

======
[1] https://commitfest.postgresql.org/46/2377/

Kind Regards,
Peter Smith.





^ permalink  raw  reply  [nested|flat] 70+ messages in thread


end of thread, other threads:[~2024-01-22 01:16 UTC | newest]

Thread overview: 70+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-03-05 16:18 Re: pg_ls_tmpdir to show directories and shared filesets Justin Pryzby <pryzby@telsasoft.com>
2020-03-06 23:35 ` Justin Pryzby <pryzby@telsasoft.com>
2020-03-07 14:14   ` Fabien COELHO <coelho@cri.ensmp.fr>
2020-03-07 17:10     ` Justin Pryzby <pryzby@telsasoft.com>
2020-03-07 17:40       ` Fabien COELHO <coelho@cri.ensmp.fr>
2020-03-07 21:40     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-08 08:02       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2020-03-10 18:30       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-13 13:12         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-15 17:15           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2020-03-15 21:27             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-16 15:20               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2020-03-16 15:41                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-16 18:21                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2020-03-16 21:48                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-16 22:17                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Alvaro Herrera <alvherre@2ndquadrant.com>
2020-03-17 03:14                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-17 09:21                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2020-03-17 19:04                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-03-17 19:11                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
2020-03-31 20:08                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-04-12 11:53                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2020-05-03 02:42                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-05-07 15:08                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-05-26 02:10                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-06-07 08:07                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2020-06-22 01:53                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-07-15 03:08                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-07-18 20:15                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-10-28 19:34                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-11-05 13:51                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-11-23 21:14                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
2020-11-23 23:00                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
2020-11-23 23:06                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
2020-11-24 16:53                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
2020-11-29 17:21                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-12-04 17:23                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Tom Lane <tgl@sss.pgh.pa.us>
2020-12-09 16:37                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-12-23 19:17                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2020-12-23 19:27                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Stephen Frost <sfrost@snowman.net>
2021-03-15 12:47                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) David Steele <david@pgmasters.net>
2021-04-06 16:01                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2021-04-09 04:14                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2021-07-02 19:16                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2021-11-22 19:17                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Bossart, Nathan <bossartn@amazon.com>
2021-11-24 00:04                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2021-12-23 13:14                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2021-12-23 17:36                                                           ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-01-02 12:07                                                             ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2022-01-02 13:55                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2022-01-18 07:00                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Julien Rouhaud <rjuju123@gmail.com>
2022-01-25 19:27                                                               ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-03-09 16:50                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-03-10 08:45                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2022-03-12 09:13                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Fabien COELHO <coelho@cri.ensmp.fr>
2022-03-13 00:45                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
2022-03-13 00:56                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-03-14 04:53                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
2022-03-15 01:59                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
2022-03-15 02:37                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-03-15 02:44                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
2022-03-31 23:42                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-04-03 13:39                                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Greg Stark <stark@mit.edu>
2022-03-22 01:28                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Andres Freund <andres@anarazel.de>
2022-03-23 06:17                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
2022-03-26 11:23                                                                       ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Michael Paquier <michael@paquier.xyz>
2022-03-29 02:13                                                                         ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-06-24 04:35                                                                 ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2022-10-28 00:38                                                                   ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Justin Pryzby <pryzby@telsasoft.com>
2024-01-22 01:16                                                                     ` Re: pg_ls_tmpdir to show directories and shared filesets (and pg_ls_*) Peter Smith <smithpb2250@gmail.com>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox