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

