Here's a version which uses an array of directory_fctx, rather than of DIR and location. That avoids changing the data structure and collatoral implications to pg_ls_dir().
Currently, this *shows* subdirs of subdirs, but doesn't decend into them. So I think maybe toplevel subdirs should be shown, too. And maybe the is_dir flag should be re-introduced (although someone could call pg_stat_file if needed). I'm interested to hear feedback on that, although this patch still isn't great.
>From dd3b2779939fc1b396fed1fba2f7cefc9a6b1ad5 Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Fri, 27 Dec 2019 23:34:14 -0600 Subject: [PATCH v4 1/2] 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 5d4f26a..c978e15 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.7.4
>From 30031c790fe5bb358f0bb372cb2d7975e2d688aa Mon Sep 17 00:00:00 2001 From: Justin Pryzby <pryz...@telsasoft.com> Date: Sat, 14 Dec 2019 16:22:15 -0600 Subject: [PATCH v4 2/2] pg_ls_tmpdir to show directories See also 9cd92d1a33699f86aa53d44ab04cc3eb50c18d11 --- doc/src/sgml/func.sgml | 14 +++-- src/backend/utils/adt/genfile.c | 136 ++++++++++++++++++++++++++-------------- 2 files changed, 97 insertions(+), 53 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 5a98158..8abc643 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -21922,12 +21922,14 @@ 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 files in the temporary directory for + <parameter>tablespace</parameter>, list the name, size, and last modification time. + Files beneath a first-level directory are shown, and include a pathname + component of their parent directory; such files are used by parallel processes. + 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> diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index c978e15..6bfac64 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -522,12 +522,84 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS) return pg_ls_dir(fcinfo); } -/* Generic function to return a directory listing of files */ +/* Recursive helper to handle showing a first level of files beneath a subdir */ static Datum -pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok) +pg_ls_dir_files_recurse(FunctionCallInfo fcinfo, FuncCallContext *funcctx, const char *dir, bool missing_ok, bool dir_ok) +{ + bool nulls[3] = {0,}; + Datum values[3]; + + directory_fctx *fctx = (directory_fctx *) funcctx->user_fctx; + + while (1) { + struct dirent *de; + char *location; + DIR *dirdesc; + + location = fctx[1].location ? fctx[1].location : fctx[0].location; + dirdesc = fctx[1].dirdesc ? fctx[1].dirdesc : fctx[0].dirdesc; + + while ((de = ReadDir(dirdesc, location)) != NULL) + { + char path[MAXPGPATH * 2]; + HeapTuple tuple; + struct stat attrib; + + /* 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, &attrib) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", path))); + + /* Ignore anything but regular files or (if requested) dirs */ + if (S_ISDIR(attrib.st_mode)) { + /* Note: decend into dirs, but do not return a tuple for the dir itself */ + /* Do not expect dirs more than one level deep */ + if (dir_ok && !fctx[1].location) { + MemoryContext oldcontext; + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + fctx[1].location = pstrdup(path); + fctx[1].dirdesc = AllocateDir(path); + MemoryContextSwitchTo(oldcontext); + return pg_ls_dir_files_recurse(fcinfo, funcctx, path, missing_ok, false); + } + /* else: fall through and show the dir itself instead of recursing */ + } else if (!S_ISREG(attrib.st_mode)) + continue; + + if (fctx[1].location) + /* We've already catted together the paths before recursing, so find the last component */ + values[0] = CStringGetTextDatum(path+1+strlen(fctx[0].location)); + else + values[0] = CStringGetTextDatum(de->d_name); + values[1] = Int64GetDatum((int64) attrib.st_size); + values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime)); + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + if (!fctx[1].dirdesc) + break; + + FreeDir(fctx[1].dirdesc); + fctx[1].location = NULL; + fctx[1].dirdesc = NULL; + } + + FreeDir(fctx[0].dirdesc); + SRF_RETURN_DONE(funcctx); +} + +/* 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) { FuncCallContext *funcctx; - struct dirent *de; directory_fctx *fctx; if (SRF_IS_FIRSTCALL()) @@ -538,7 +610,9 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok) funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - fctx = palloc(sizeof(directory_fctx)); + /* Allocate *two* structs, members of the 2nd of which are non-NULL iff recursing */ + fctx = palloc(2*sizeof(directory_fctx)); + memset(fctx, 0, 2*sizeof(*fctx)); tupdesc = CreateTemplateTupleDesc(3); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", @@ -549,10 +623,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok) TIMESTAMPTZOID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); - fctx->location = pstrdup(dir); - fctx->dirdesc = AllocateDir(fctx->location); + fctx[0].location = pstrdup(dir); + fctx[0].dirdesc = AllocateDir(dir); - if (!fctx->dirdesc) + if (!fctx[0].dirdesc) { if (missing_ok && errno == ENOENT) { @@ -563,7 +637,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))); + fctx[0].location))); } funcctx->user_fctx = fctx; @@ -571,60 +645,28 @@ 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) - { - Datum values[3]; - bool nulls[3]; - char path[MAXPGPATH * 2]; - struct stat attrib; - 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)); - - tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); - SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); - } - - FreeDir(fctx->dirdesc); - SRF_RETURN_DONE(funcctx); + /* NULL could mean either DONE or error. We skip dirs with errors, so no need to distinguish in the case of error */ + return pg_ls_dir_files_recurse(fcinfo, funcctx, dir, missing_ok, dir_ok); } /* Function to return the list of files in the log directory */ 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); } /* * Generic function to return the list of files in pgsql_tmp + * Files are also shown one level deep, with their subdir prefix. */ static Datum pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc) @@ -638,7 +680,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 +709,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); } -- 2.7.4