On Fri, Jun 19, 2015 at 9:22 PM, Robert Haas <robertmh...@gmail.com> wrote: > On Fri, Jun 19, 2015 at 8:18 AM, Robert Haas <robertmh...@gmail.com> wrote: >> On Fri, Jun 19, 2015 at 12:14 AM, Michael Paquier >> <michael.paqu...@gmail.com> wrote: >>>> Listing the directories with pg_ls_dir() has the same problem. >>> >>> (After some discussion on IM with Heikki on this one). >>> This is actually more tricky because pg_ls_dir() does not return '.' >>> or '..' that we could use to identify that the directory actually >>> exists or not when it is empty. Hence I think that we should add two >>> options to pg_ls_dir: >>> - include_self, default to false. If set to true, '.' is added in the >>> list of items. >>> - if_not_exists, to bypass error that a folder does not exist, default >>> at false. If if_not_exists = true and include_self = true, returning >>> only '.' would mean that the folder exist but that it is empty. If >>> if_not_exists = true and include_self = false, no rows are returned. >>> We could as well ERROR as well if both options are set like that. I am >>> fine with any of them as long as behavior is properly documented. >> >> Including '.' to distinguish between an empty directory and a >> nonexistent one seems like an unnecessarily complicated and >> non-obvious API. How about just one additional parameter bool >> *exists. If NULL and no directory, ERROR, else on return set *exists >> to true or false. > > Err, wait. You're talking about an SQL function, heh heh. So that > won't work. Maybe what you proposed is the best we can do, then, > although I would suggest that you rename the include_self parameter to > something like include_dot_dirs and return both "." and "..". > Returning only "." seems like it will seem weird to people.
So... Attached are a set of patches dedicated at fixing this issue: - 0001, add if_not_exists to pg_tablespace_location, returning NULL if path does not exist - 0002, same with pg_stat_file, returning NULL if file does not exist - 0003, same with pg_read_*file. I added them to all the existing functions for consistency. - 0004, pg_ls_dir extended with if_not_exists and include_dot_dirs (thanks Robert for the naming!) - 0005, as things get complex, a set of regression tests aimed to covering those things. pg_tablespace_location is platform-dependent, so there are no tests for it. - 0006, the fix for pg_rewind, using what has been implemented before. Attached is an updated test case triggering the issue (rewind_test.bash), with the small patch attached that adds a pg_sleep call in pg_rewind.c (20150623_pg_rewind_sleep.patch). I imagine that this is a bug people are going to meet in the field easily, particularly with temporary relation files or temporary XLOG files. Regards, -- Michael
From 454fa9e67c67621b0832d7fbd90153328c99197b Mon Sep 17 00:00:00 2001 From: Michael Paquier <michael@otacoo.com> Date: Tue, 23 Jun 2015 11:45:49 +0900 Subject: [PATCH 1/6] Extend pg_tablespace_location with if_not_exists option This is useful for code paths that prefer receive an empty response instead of an ERROR if tablespace specified does not exist. --- doc/src/sgml/func.sgml | 8 ++++-- src/backend/utils/adt/misc.c | 60 ++++++++++++++++++++++++++++++++++--------- src/include/catalog/pg_proc.h | 2 ++ src/include/utils/builtins.h | 1 + 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 650051b..7929e7e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -15844,9 +15844,13 @@ SELECT pg_type_is_visible('myschema.widget'::regtype); <entry>get the set of database OIDs that have objects in the tablespace</entry> </row> <row> - <entry><literal><function>pg_tablespace_location(<parameter>tablespace_oid</parameter>)</function></literal></entry> + <entry><literal><function>pg_tablespace_location(<parameter>tablespace_oid</parameter> [, <parameter>if_not_exists</> <type>boolean</type>])</function></literal></entry> <entry><type>text</type></entry> - <entry>get the path in the file system that this tablespace is located in</entry> + <entry> + Get the path in the file system that this tablespace is located in. + If <parameter>if_not_exists</parameter> is true, <literal>NULL</literal> + is returned if tablespace does not exist instead of an error. + </entry> </row> <row> <entry><literal><function>pg_typeof(<parameter>any</parameter>)</function></literal></entry> diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index c0495d9..034728d 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -336,17 +336,24 @@ pg_tablespace_databases(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } - /* - * pg_tablespace_location - get location for a tablespace + * Wrapper function for pg_tablespace_location and + * pg_tablespace_location_extended. */ -Datum -pg_tablespace_location(PG_FUNCTION_ARGS) +static Datum +tablespace_location_wrapper(FunctionCallInfo fcinfo) { Oid tablespaceOid = PG_GETARG_OID(0); char sourcepath[MAXPGPATH]; char targetpath[MAXPGPATH]; int rllen; + bool if_not_exists = false; + + /* + * Check for IF NOT EXISTS option. + */ + if (PG_NARGS() == 2 && !PG_ARGISNULL(1)) + if_not_exists = PG_GETARG_BOOL(1); /* * It's useful to apply this function to pg_class.reltablespace, wherein @@ -373,15 +380,25 @@ pg_tablespace_location(PG_FUNCTION_ARGS) rllen = readlink(sourcepath, targetpath, sizeof(targetpath)); if (rllen < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not read symbolic link \"%s\": %m", - sourcepath))); + { + if (!if_not_exists) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read symbolic link \"%s\": %m", + sourcepath))); + else + PG_RETURN_NULL(); + } if (rllen >= sizeof(targetpath)) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("symbolic link \"%s\" target is too long", - sourcepath))); + { + if (!if_not_exists) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("symbolic link \"%s\" target is too long", + sourcepath))); + else + PG_RETURN_NULL(); + } targetpath[rllen] = '\0'; PG_RETURN_TEXT_P(cstring_to_text(targetpath)); @@ -394,6 +411,25 @@ pg_tablespace_location(PG_FUNCTION_ARGS) } /* + * pg_tablespace_location - get location for a tablespace + */ +Datum +pg_tablespace_location(PG_FUNCTION_ARGS) +{ + return tablespace_location_wrapper(fcinfo); +} + +/* + * pg_tablespace_location - get location for a tablespace, with option to + * bypass errors in case of a non-existing tablespace. + */ +Datum +pg_tablespace_location_extended(PG_FUNCTION_ARGS) +{ + return tablespace_location_wrapper(fcinfo); +} + +/* * pg_sleep - delay for N seconds */ Datum diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 6b3d194..1f163f0 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -2922,6 +2922,8 @@ DESCR("current trigger depth"); DATA(insert OID = 3778 ( pg_tablespace_location PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_tablespace_location _null_ _null_ _null_ )); DESCR("tablespace location"); +DATA(insert OID = 3579 ( pg_tablespace_location PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "26 16" _null_ _null_ _null_ _null_ _null_ pg_tablespace_location_extended _null_ _null_ _null_ )); +DESCR("tablespace location"); DATA(insert OID = 1946 ( encode PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "17 25" _null_ _null_ _null_ _null_ _null_ binary_encode _null_ _null_ _null_ )); DESCR("convert bytea value into some ascii-only text string"); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 51f25a2..ab78ec2 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -486,6 +486,7 @@ extern Datum pg_terminate_backend(PG_FUNCTION_ARGS); extern Datum pg_reload_conf(PG_FUNCTION_ARGS); extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS); extern Datum pg_tablespace_location(PG_FUNCTION_ARGS); +extern Datum pg_tablespace_location_extended(PG_FUNCTION_ARGS); extern Datum pg_rotate_logfile(PG_FUNCTION_ARGS); extern Datum pg_sleep(PG_FUNCTION_ARGS); extern Datum pg_get_keywords(PG_FUNCTION_ARGS); -- 2.4.4
From 72ef91958e91e9d6ba4a72d325872edb65166cc7 Mon Sep 17 00:00:00 2001 From: Michael Paquier <michael@otacoo.com> Date: Tue, 23 Jun 2015 11:48:34 +0900 Subject: [PATCH 2/6] Extend pg_stat_file with if_not_exists option This is useful for code paths that prefer receiving a NULL result instead of an ERROR if file specified does not exist. --- doc/src/sgml/func.sgml | 9 ++++-- src/backend/utils/adt/genfile.c | 65 +++++++++++++++++++++++++++++++---------- src/include/catalog/pg_proc.h | 2 ++ src/include/utils/builtins.h | 1 + 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 7929e7e..0f70985 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17830,10 +17830,15 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); </row> <row> <entry> - <literal><function>pg_stat_file(<parameter>filename</> <type>text</>)</function></literal> + <literal><function>pg_stat_file(<parameter>filename</> <type>text</>[, <parameter>if_not_exists</> <type>boolean</type>])</function></literal> </entry> <entry><type>record</type></entry> - <entry>Return information about a file</entry> + <entry> + Return information about a file. If + <parameter>if_not_exists</parameter> is true, <literal>NULL</literal> + is returned for all result fields if file does not exist instead of + an error. + </entry> </row> </tbody> </tgroup> diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index f3f3cca..93eb379 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -254,10 +254,10 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS) } /* - * stat a file + * Wrapper for pg_stat_file and pg_stat_file_extended. */ -Datum -pg_stat_file(PG_FUNCTION_ARGS) +static Datum +stat_file_wrapper(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); char *filename; @@ -266,18 +266,29 @@ pg_stat_file(PG_FUNCTION_ARGS) bool isnull[6]; HeapTuple tuple; TupleDesc tupdesc; + bool if_not_exists = false; + bool return_null = false; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to get file information")))); + /* Check for option if_not_exists */ + if (PG_NARGS() == 2 && !PG_ARGISNULL(1)) + if_not_exists = PG_GETARG_BOOL(1); + filename = convert_and_check_filename(filename_t); if (stat(filename, &fst) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", filename))); + { + if (if_not_exists) + return_null = true; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + } /* * This record type had better match the output parameters declared for me @@ -298,20 +309,23 @@ pg_stat_file(PG_FUNCTION_ARGS) "isdir", BOOLOID, -1, 0); BlessTupleDesc(tupdesc); - memset(isnull, false, sizeof(isnull)); + memset(isnull, return_null, 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 (!return_null) + { + 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; + 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)); + 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)); + } tuple = heap_form_tuple(tupdesc, values, isnull); @@ -320,6 +334,25 @@ pg_stat_file(PG_FUNCTION_ARGS) PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); } +/* + * pg_stat_file_extended + * Stat a file, with possibility to bypass error in case of a missing file. + */ +Datum +pg_stat_file_extended(PG_FUNCTION_ARGS) +{ + return stat_file_wrapper(fcinfo); +} + +/* + * stat a file + */ +Datum +pg_stat_file(PG_FUNCTION_ARGS) +{ + return stat_file_wrapper(fcinfo); +} + /* * List a directory (returns the filenames only) diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 1f163f0..8da1781 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3185,6 +3185,8 @@ DESCR("rotate log file"); DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ _null_ pg_stat_file _null_ _null_ _null_ )); DESCR("get information about file"); +DATA(insert OID = 3307 ( pg_stat_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 2249 "25 16" "{25,16,20,1184,1184,1184,1184,16}" "{i,i,o,o,o,o,o,o}" "{filename,if_not_exists,size,access,modification,change,creation,isdir}" _null_ _null_ pg_stat_file_extended _null_ _null_ _null_ )); +DESCR("get information about file"); DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); DESCR("read text from a file"); DATA(insert OID = 3826 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_read_file_all _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index ab78ec2..e46650a 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -472,6 +472,7 @@ extern Datum pg_relation_filepath(PG_FUNCTION_ARGS); extern bytea *read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read); extern Datum pg_stat_file(PG_FUNCTION_ARGS); +extern Datum pg_stat_file_extended(PG_FUNCTION_ARGS); extern Datum pg_read_file(PG_FUNCTION_ARGS); extern Datum pg_read_file_all(PG_FUNCTION_ARGS); extern Datum pg_read_binary_file(PG_FUNCTION_ARGS); -- 2.4.4
From b4e067ccefef8aac912211aee792af91c624b553 Mon Sep 17 00:00:00 2001 From: Michael Paquier <michael@otacoo.com> Date: Tue, 23 Jun 2015 11:51:30 +0900 Subject: [PATCH 3/6] Add IF NOT EXISTS to pg_read_file and pg_read_binary_file This is useful for code paths that do not want to fail if the file queried does not exist. --- doc/src/sgml/func.sgml | 16 +++- src/backend/commands/extension.c | 2 +- src/backend/utils/adt/genfile.c | 174 +++++++++++++++++++++++++++++++++------ src/include/catalog/pg_proc.h | 8 ++ src/include/utils/builtins.h | 8 +- 5 files changed, 179 insertions(+), 29 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 0f70985..edfea8b 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17816,17 +17816,25 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); </row> <row> <entry> - <literal><function>pg_read_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</>])</function></literal> + <literal><function>pg_read_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</> [, <parameter>if_not_exists</> <type>boolean</>] ])</function></literal> </entry> <entry><type>text</type></entry> - <entry>Return the contents of a text file</entry> + <entry> + Return the contents of a text file. If + <parameter>if_not_exists</parameter> is true, <literal>NULL</literal> + is returned if file does not exist instead of an error. + </entry> </row> <row> <entry> - <literal><function>pg_read_binary_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</>])</function></literal> + <literal><function>pg_read_binary_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</> [, <parameter>if_not_exists</> <type>boolean</>] ])</function></literal> </entry> <entry><type>bytea</type></entry> - <entry>Return the contents of a file</entry> + <entry> + Return the contents of a file. If + <parameter>if_not_exists</parameter> is true, <literal>NULL</literal> + is returned if file does not exist instead of an error. + </entry> </row> <row> <entry> diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 5cc74d0..25bbe01 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -640,7 +640,7 @@ read_extension_script_file(const ExtensionControlFile *control, char *dest_str; int len; - content = read_binary_file(filename, 0, -1); + content = read_binary_file(filename, 0, -1, false); /* use database encoding if not given */ if (control->encoding < 0) diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index 93eb379..6ce9ec3 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -88,7 +88,8 @@ convert_and_check_filename(text *arg) * We read the whole of the file when bytes_to_read is negative. */ bytea * -read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) +read_binary_file(const char *filename, int64 seek_offset, + int64 bytes_to_read, bool if_not_exists) { bytea *buf; size_t nbytes; @@ -103,9 +104,14 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) struct stat fst; if (stat(filename, &fst) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", filename))); + { + if (if_not_exists) + return NULL; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + } bytes_to_read = fst.st_size - seek_offset; } @@ -118,10 +124,15 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) errmsg("requested length too large"))); if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open file \"%s\" for reading: %m", - filename))); + { + if (if_not_exists) + return NULL; + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for reading: %m", + filename))); + } if (fseeko(file, (off_t) seek_offset, (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0) @@ -150,11 +161,16 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read) * in the database encoding. */ static text * -read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read) +read_text_file(const char *filename, int64 seek_offset, + int64 bytes_to_read, bool if_not_exists) { bytea *buf; - buf = read_binary_file(filename, seek_offset, bytes_to_read); + buf = read_binary_file(filename, seek_offset, + bytes_to_read, if_not_exists); + + if (buf == NULL) + return NULL; /* Make sure the input is valid */ pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false); @@ -164,21 +180,27 @@ read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read) } /* - * Read a section of a file, returning it as text + * Wrapper function for pg_read_file and pg_read_file_extended. */ -Datum -pg_read_file(PG_FUNCTION_ARGS) +static Datum +read_file_wrapper(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); int64 seek_offset = PG_GETARG_INT64(1); int64 bytes_to_read = PG_GETARG_INT64(2); + bool if_not_exists = false; char *filename; + bytea *result; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to read files")))); + /* Check for option if_not_exists */ + if (PG_NARGS() == 4 && !PG_ARGISNULL(3)) + if_not_exists = PG_GETARG_BOOL(3); + filename = convert_and_check_filename(filename_t); if (bytes_to_read < 0) @@ -186,44 +208,100 @@ pg_read_file(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested length cannot be negative"))); - PG_RETURN_TEXT_P(read_text_file(filename, seek_offset, bytes_to_read)); + result = read_text_file(filename, seek_offset, bytes_to_read, + if_not_exists); + if (result == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(result); } /* - * Read the whole of a file, returning it as text + * Read a section of a file, returning it as text */ Datum -pg_read_file_all(PG_FUNCTION_ARGS) +pg_read_file(PG_FUNCTION_ARGS) +{ + return read_file_wrapper(fcinfo); +} + +/* + * Similar to pg_read_file, with IF NOT EXISTS option. + */ +Datum +pg_read_file_extended(PG_FUNCTION_ARGS) +{ + return read_file_wrapper(fcinfo); +} + +/* + * Wrapper function for pg_read_file_all and pg_read_file_all_extended. + */ +static Datum +read_file_all_wrapper(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); char *filename; + text *result; + bool if_not_exists = false; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to read files")))); + /* Check for option if_not_exists */ + if (PG_NARGS() == 2 && !PG_ARGISNULL(1)) + if_not_exists = PG_GETARG_BOOL(1); + filename = convert_and_check_filename(filename_t); - PG_RETURN_TEXT_P(read_text_file(filename, 0, -1)); + result = read_text_file(filename, 0, -1, if_not_exists); + if (result == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(result); } /* - * Read a section of a file, returning it as bytea + * Read the whole of a file, returning it as text */ Datum -pg_read_binary_file(PG_FUNCTION_ARGS) +pg_read_file_all(PG_FUNCTION_ARGS) +{ + return read_file_all_wrapper(fcinfo); +} + +/* + * Similar to pg_read_file_all, with IF NOT EXISTS option. + */ +Datum +pg_read_file_all_extended(PG_FUNCTION_ARGS) +{ + return read_file_all_wrapper(fcinfo); +} + +/* + * Wrapper for pg_read_binary_file and pg_read_binary_file_extended. + */ +static Datum +read_binary_file_wrapper(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); int64 seek_offset = PG_GETARG_INT64(1); int64 bytes_to_read = PG_GETARG_INT64(2); char *filename; + bool if_not_exists = false; + bytea *result; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to read files")))); + /* Check for option if_not_exists */ + if (PG_NARGS() == 4 && !PG_ARGISNULL(3)) + if_not_exists = PG_GETARG_BOOL(3); + filename = convert_and_check_filename(filename_t); if (bytes_to_read < 0) @@ -231,26 +309,76 @@ pg_read_binary_file(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("requested length cannot be negative"))); - PG_RETURN_BYTEA_P(read_binary_file(filename, seek_offset, bytes_to_read)); + result = read_binary_file(filename, seek_offset, + bytes_to_read, if_not_exists); + if (result == NULL) + PG_RETURN_NULL(); + PG_RETURN_BYTEA_P(result); } /* - * Read the whole of a file, returning it as bytea + * Read a section of a file, returning it as bytea */ Datum -pg_read_binary_file_all(PG_FUNCTION_ARGS) +pg_read_binary_file(PG_FUNCTION_ARGS) +{ + return read_binary_file_wrapper(fcinfo); +} + +/* + * Similar to pg_read_binary_file, with IF NOT EXISTS option. + */ +Datum +pg_read_binary_file_extended(PG_FUNCTION_ARGS) +{ + return read_binary_file_wrapper(fcinfo); +} + +/* + * Wrapper for pg_read_binary_file_all and pg_read_binary_file_all_extended. + */ +static Datum +read_binary_file_all_wrapper(PG_FUNCTION_ARGS) { text *filename_t = PG_GETARG_TEXT_P(0); char *filename; + bool if_not_exists = false; + text *result; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to read files")))); + /* Check for option if_not_exists */ + if (PG_NARGS() == 2 && !PG_ARGISNULL(1)) + if_not_exists = PG_GETARG_BOOL(1); + filename = convert_and_check_filename(filename_t); - PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1)); + result = read_binary_file(filename, 0, -1, if_not_exists); + if (result == NULL) + PG_RETURN_NULL(); + + PG_RETURN_BYTEA_P(result); +} + +/* + * Read the whole of a file, returning it as bytea + */ +Datum +pg_read_binary_file_all(PG_FUNCTION_ARGS) +{ + return read_binary_file_all_wrapper(fcinfo); +} + +/* + * Similar to pg_read_binary_file_all, with IF NOT EXISTS option. + */ +Datum +pg_read_binary_file_all_extended(PG_FUNCTION_ARGS) +{ + return read_binary_file_all_wrapper(fcinfo); } /* diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 8da1781..c805a9a 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3189,12 +3189,20 @@ DATA(insert OID = 3307 ( pg_stat_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 DESCR("get information about file"); DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); DESCR("read text from a file"); +DATA(insert OID = 3293 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 4 0 25 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_file_extended _null_ _null_ _null_ )); +DESCR("read text from a file"); DATA(insert OID = 3826 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_read_file_all _null_ _null_ _null_ )); DESCR("read text from a file"); +DATA(insert OID = 3294 ( pg_read_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 25 "25 16" _null_ _null_ _null_ _null_ _null_ pg_read_file_all_extended _null_ _null_ _null_ )); +DESCR("read text from a file"); DATA(insert OID = 3827 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 17 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file _null_ _null_ _null_ )); DESCR("read bytea from a file"); +DATA(insert OID = 3295 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 4 0 17 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_extended _null_ _null_ _null_ )); +DESCR("read bytea from a file"); DATA(insert OID = 3828 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 17 "25" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_all _null_ _null_ _null_ )); DESCR("read bytea from a file"); +DATA(insert OID = 3296 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 17 "25 16" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_all_extended _null_ _null_ _null_ )); +DESCR("read bytea from a file"); DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 0 f f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); DESCR("list all files in a directory"); DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index e46650a..82d0e18 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -470,13 +470,19 @@ extern Datum pg_relation_filepath(PG_FUNCTION_ARGS); /* genfile.c */ extern bytea *read_binary_file(const char *filename, - int64 seek_offset, int64 bytes_to_read); + int64 seek_offset, + int64 bytes_to_read, + bool if_not_exists); extern Datum pg_stat_file(PG_FUNCTION_ARGS); extern Datum pg_stat_file_extended(PG_FUNCTION_ARGS); extern Datum pg_read_file(PG_FUNCTION_ARGS); +extern Datum pg_read_file_extended(PG_FUNCTION_ARGS); extern Datum pg_read_file_all(PG_FUNCTION_ARGS); +extern Datum pg_read_file_all_extended(PG_FUNCTION_ARGS); extern Datum pg_read_binary_file(PG_FUNCTION_ARGS); +extern Datum pg_read_binary_file_extended(PG_FUNCTION_ARGS); extern Datum pg_read_binary_file_all(PG_FUNCTION_ARGS); +extern Datum pg_read_binary_file_all_extended(PG_FUNCTION_ARGS); extern Datum pg_ls_dir(PG_FUNCTION_ARGS); /* misc.c */ -- 2.4.4
From f40a675db5e086a72e52cc9d0394728a0ada95f8 Mon Sep 17 00:00:00 2001 From: Michael Paquier <michael@otacoo.com> Date: Mon, 22 Jun 2015 21:19:33 +0900 Subject: [PATCH 4/6] Extend pg_ls_dir with include_dot_dirs and if_not_exists If include_dot_dirs is set to true, "." and ".." are listed in the list of files listed. If if_not_exists is true, an empty list of files is returned to caller instead of erroring out if the folder specified does not exist. --- doc/src/sgml/func.sgml | 10 +++++-- src/backend/utils/adt/genfile.c | 59 +++++++++++++++++++++++++++++++++-------- src/include/catalog/pg_proc.h | 2 ++ src/include/utils/builtins.h | 1 + 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index edfea8b..e9dc19f 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17809,10 +17809,16 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); <tbody> <row> <entry> - <literal><function>pg_ls_dir(<parameter>dirname</> <type>text</>)</function></literal> + <literal><function>pg_ls_dir(<parameter>dirname</> <type>text</> [, <parameter>if_not_exists</> <type>boolean</>, <parameter>include_dot_dirs</> <type>boolean</>] )</function></literal> </entry> <entry><type>setof text</type></entry> - <entry>List the contents of a directory</entry> + <entry> + List the contents of a directory. If + <parameter>if_not_exists</parameter> is true, an empty result is + returned instead of an error. If <parameter>include_dot_dirs</> + is true, <quote>.</> and <quote>..</> are included as well in + the returned list. + </entry> </row> <row> <entry> diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index 6ce9ec3..6af7253 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -35,6 +35,7 @@ typedef struct { char *location; DIR *dirdesc; + bool include_dot_dirs; } directory_fctx; @@ -483,14 +484,15 @@ pg_stat_file(PG_FUNCTION_ARGS) /* - * List a directory (returns the filenames only) + * Wrapper for pg_ls_dir and pg_ls_dir_extended. */ -Datum -pg_ls_dir(PG_FUNCTION_ARGS) +static Datum +ls_dir_wrapper(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; struct dirent *de; directory_fctx *fctx; + MemoryContext oldcontext; if (!superuser()) ereport(ERROR, @@ -499,7 +501,17 @@ pg_ls_dir(PG_FUNCTION_ARGS) if (SRF_IS_FIRSTCALL()) { - MemoryContext oldcontext; + bool if_not_exists = false; + bool include_dot_dirs = false; + + /* see if custom parameters have been passed down */ + if (PG_NARGS() == 3) + { + if (!PG_ARGISNULL(1)) + if_not_exists = 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); @@ -508,13 +520,18 @@ pg_ls_dir(PG_FUNCTION_ARGS) fctx->location = convert_and_check_filename(PG_GETARG_TEXT_P(0)); fctx->dirdesc = AllocateDir(fctx->location); + fctx->include_dot_dirs = include_dot_dirs; if (!fctx->dirdesc) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not open directory \"%s\": %m", - fctx->location))); - + { + if (if_not_exists && errno == ENOENT) + 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); } @@ -524,8 +541,9 @@ pg_ls_dir(PG_FUNCTION_ARGS) while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL) { - if (strcmp(de->d_name, ".") == 0 || - strcmp(de->d_name, "..") == 0) + if (!fctx->include_dot_dirs && + (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0)) continue; SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name)); @@ -535,3 +553,22 @@ pg_ls_dir(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } + +/* + * List a directory (returns the filenames only) + */ +Datum +pg_ls_dir(PG_FUNCTION_ARGS) +{ + return ls_dir_wrapper(fcinfo); +} + +/* + * List a directory (can optionally return dot directories or + * use IF NOT EXISTS). + */ +Datum +pg_ls_dir_extended(PG_FUNCTION_ARGS) +{ + return ls_dir_wrapper(fcinfo); +} diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index c805a9a..b559572 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3205,6 +3205,8 @@ DATA(insert OID = 3296 ( pg_read_binary_file PGNSP PGUID 12 1 0 0 0 f f f f t f DESCR("read bytea from a file"); DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 0 f f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); DESCR("list all files in a directory"); +DATA(insert OID = 3297 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 0 f f f f t t v 3 0 25 "25 16 16" _null_ _null_ _null_ _null_ _null_ pg_ls_dir_extended _null_ _null_ _null_ )); +DESCR("list all files in a directory"); DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ )); DESCR("sleep for the specified time in seconds"); DATA(insert OID = 3935 ( pg_sleep_for PGNSP PGUID 14 1 0 0 0 f f f f t f v 1 0 2278 "1186" _null_ _null_ _null_ _null_ _null_ "select pg_catalog.pg_sleep(extract(epoch from pg_catalog.clock_timestamp() operator(pg_catalog.+) $1) operator(pg_catalog.-) extract(epoch from pg_catalog.clock_timestamp()))" _null_ _null_ _null_ )); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 82d0e18..446cda6 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -484,6 +484,7 @@ extern Datum pg_read_binary_file_extended(PG_FUNCTION_ARGS); extern Datum pg_read_binary_file_all(PG_FUNCTION_ARGS); extern Datum pg_read_binary_file_all_extended(PG_FUNCTION_ARGS); extern Datum pg_ls_dir(PG_FUNCTION_ARGS); +extern Datum pg_ls_dir_extended(PG_FUNCTION_ARGS); /* misc.c */ extern Datum current_database(PG_FUNCTION_ARGS); -- 2.4.4
From b15d22e38f0a27e0fedf3fb07e26dcb580f42cb9 Mon Sep 17 00:00:00 2001 From: Michael Paquier <michael@otacoo.com> Date: Tue, 23 Jun 2015 10:42:48 +0900 Subject: [PATCH 5/6] Add regression tests for pg_ls_dir and pg_read_[binary_]file With the new options if_not_exists that have been added, this looks worth adding. --- src/test/regress/expected/file.out | 84 ++++++++++++++++++++++++++++++++++++++ src/test/regress/parallel_schedule | 3 ++ src/test/regress/sql/file.sql | 29 +++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/test/regress/expected/file.out create mode 100644 src/test/regress/sql/file.sql diff --git a/src/test/regress/expected/file.out b/src/test/regress/expected/file.out new file mode 100644 index 0000000..0ddae30 --- /dev/null +++ b/src/test/regress/expected/file.out @@ -0,0 +1,84 @@ +-- +-- FILE +-- +-- Tests of SQL functions interacting directly with files of PGDATA +-- pg_read_file +SELECT pg_read_file('postgresql.auto.conf', 0, 23); -- OK + pg_read_file +------------------------- + # Do not edit this file +(1 row) + +SELECT pg_read_file('postgresql_not_exists', 0, 23); -- not OK +ERROR: could not open file "postgresql_not_exists" for reading: No such file or directory +SELECT pg_read_file('postgresql_not_exists', 0, 23, false); -- not OK +ERROR: could not open file "postgresql_not_exists" for reading: No such file or directory +SELECT pg_read_file('postgresql_not_exists', 0, 23, true); -- OK + pg_read_file +-------------- + +(1 row) + +-- pg_read_binary_file +SELECT pg_read_binary_file('postgresql.auto.conf', 0, 23); -- OK + pg_read_binary_file +-------------------------------------------------- + \x2320446f206e6f74206564697420746869732066696c65 +(1 row) + +SELECT pg_read_binary_file('postgresql_not_exists', 0, 23); -- not OK +ERROR: could not open file "postgresql_not_exists" for reading: No such file or directory +SELECT pg_read_binary_file('postgresql_not_exists', 0, 23, false); -- not OK +ERROR: could not open file "postgresql_not_exists" for reading: No such file or directory +SELECT pg_read_binary_file('postgresql_not_exists', 0, 23, true); -- OK + pg_read_binary_file +--------------------- + +(1 row) + +-- pg_ls_dir +SELECT pg_ls_dir('postgresql.conf'); -- not OK, not a directory +ERROR: could not open directory "postgresql.conf": Not a directory +SELECT pg_ls_dir('pg_twophase'); -- empty directory + pg_ls_dir +----------- +(0 rows) + +SELECT pg_ls_dir('pg_twophase', true, true); -- OK, not empty list + pg_ls_dir +----------- + . + .. +(2 rows) + +SELECT pg_ls_dir('pg_twophase', false, false); -- OK, empty list + pg_ls_dir +----------- +(0 rows) + +SELECT pg_ls_dir('pg_twophase', false, true); -- OK, not empty list + pg_ls_dir +----------- + . + .. +(2 rows) + +SELECT pg_ls_dir('pg_twophase', true, false); -- OK, empty list + pg_ls_dir +----------- +(0 rows) + +SELECT pg_ls_dir('postgresql_not_exists', false, true); -- not OK +ERROR: could not open directory "postgresql_not_exists": No such file or directory +SELECT pg_ls_dir('postgresql_not_exists', false, false); -- not OK +ERROR: could not open directory "postgresql_not_exists": No such file or directory +SELECT pg_ls_dir('postgresql_not_exists', true, true); -- OK, empty list + pg_ls_dir +----------- +(0 rows) + +SELECT pg_ls_dir('postgresql_not_exists', true, false); -- OK, empty list + pg_ls_dir +----------- +(0 rows) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 91780cd..b1739fa 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -108,5 +108,8 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c # event triggers cannot run concurrently with any test that runs DDL test: event_trigger +# Files read during this test may be modified in parallel by other tests +test: file + # run stats by itself because its delay may be insufficient under heavy load test: stats diff --git a/src/test/regress/sql/file.sql b/src/test/regress/sql/file.sql new file mode 100644 index 0000000..a0238f2 --- /dev/null +++ b/src/test/regress/sql/file.sql @@ -0,0 +1,29 @@ +-- +-- FILE +-- + +-- Tests of SQL functions interacting directly with files of PGDATA + +-- pg_read_file +SELECT pg_read_file('postgresql.auto.conf', 0, 23); -- OK +SELECT pg_read_file('postgresql_not_exists', 0, 23); -- not OK +SELECT pg_read_file('postgresql_not_exists', 0, 23, false); -- not OK +SELECT pg_read_file('postgresql_not_exists', 0, 23, true); -- OK + +-- pg_read_binary_file +SELECT pg_read_binary_file('postgresql.auto.conf', 0, 23); -- OK +SELECT pg_read_binary_file('postgresql_not_exists', 0, 23); -- not OK +SELECT pg_read_binary_file('postgresql_not_exists', 0, 23, false); -- not OK +SELECT pg_read_binary_file('postgresql_not_exists', 0, 23, true); -- OK + +-- pg_ls_dir +SELECT pg_ls_dir('postgresql.conf'); -- not OK, not a directory +SELECT pg_ls_dir('pg_twophase'); -- empty directory +SELECT pg_ls_dir('pg_twophase', true, true); -- OK, not empty list +SELECT pg_ls_dir('pg_twophase', false, false); -- OK, empty list +SELECT pg_ls_dir('pg_twophase', false, true); -- OK, not empty list +SELECT pg_ls_dir('pg_twophase', true, false); -- OK, empty list +SELECT pg_ls_dir('postgresql_not_exists', false, true); -- not OK +SELECT pg_ls_dir('postgresql_not_exists', false, false); -- not OK +SELECT pg_ls_dir('postgresql_not_exists', true, true); -- OK, empty list +SELECT pg_ls_dir('postgresql_not_exists', true, false); -- OK, empty list -- 2.4.4
From f12a2cec0489d6c20ed55665bc6698ac701e5d73 Mon Sep 17 00:00:00 2001 From: Michael Paquier <michael@otacoo.com> Date: Tue, 23 Jun 2015 13:16:23 +0900 Subject: [PATCH 6/6] Make pg_rewind able to detect deleted files on remote source When getting a list of PGDATA files from a remote server with pg_rewind, it is possible that this list contains temporary files or files that have been deleted on the remote server after taking the file list, like what DROP TABLE would do for a relation. This can lead to errors when calling pg_read_binary_file on an entry that is listed but does not exist anymore, preventing the rewind from working correctly. In order to prevent such problems, use the new if_not_exists options in pg_ls_dir, pg_tablespace_location and pg_read_binary_file to not fail if a file does not exist anymore and continue process as for example a relation file removed does not prevent a rewound node from replaying WAL in recovery, node rewound guaranteed to have a minimum recovery point set to the current WAL location of the remote node that is put in sync with. --- src/bin/pg_rewind/libpq_fetch.c | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c index 6ffd24e..2051d30 100644 --- a/src/bin/pg_rewind/libpq_fetch.c +++ b/src/bin/pg_rewind/libpq_fetch.c @@ -149,18 +149,24 @@ libpqProcessFileList(void) sql = "WITH RECURSIVE files (path, filename, size, isdir) AS (\n" " SELECT '' AS path, filename, size, isdir FROM\n" - " (SELECT pg_ls_dir('.') AS filename) AS fn,\n" - " pg_stat_file(fn.filename) AS this\n" + " (SELECT filename\n" + " FROM pg_ls_dir('.', true, true) AS filename\n" + " WHERE filename NOT IN ('.', '..')) AS fn,\n" + " pg_stat_file(fn.filename, true) AS this\n" + " WHERE this.size IS NOT NULL\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) AS fn,\n" - " pg_stat_file(parent.path || parent.filename || '/' || fn) AS this\n" - " WHERE parent.isdir = 't'\n" - ")\n" - "SELECT path || filename, size, isdir,\n" - " pg_tablespace_location(pg_tablespace.oid) AS link_target\n" + " pg_ls_dir(parent.path || parent.filename, true, true) AS fn,\n" + " pg_stat_file(parent.path || parent.filename || '/' || fn, true)\n" + " AS this\n" + " WHERE parent.isdir = 't' AND\n" + " (fn NOT IN ('.', '..')) AND\n" + " this.size IS NOT NULL)\n" + "SELECT path || filename AS file_path, size, isdir,\n" + " pg_tablespace_location(pg_tablespace.oid, true)\n" + " AS link_target\n" "FROM files\n" "LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc/'\n" " AND oid::text = files.filename\n"; @@ -259,8 +265,7 @@ receiveFileChunks(const char *sql) } if (PQgetisnull(res, 0, 0) || - PQgetisnull(res, 0, 1) || - PQgetisnull(res, 0, 2)) + PQgetisnull(res, 0, 1)) { pg_fatal("unexpected NULL result while fetching remote files\n"); } @@ -278,6 +283,20 @@ receiveFileChunks(const char *sql) memcpy(filename, PQgetvalue(res, 0, 0), filenamelen); filename[filenamelen] = '\0'; + /* + * It may be possible that a file has been deleted on remote side after + * creating the file map. In this case simply ignore it and move on. + */ + if (PQgetisnull(res, 0, 2)) + { + pg_log(PG_DEBUG, + "received NULL chunk for file \"%s\", file has been deleted\n", + filename); + pg_free(filename); + PQclear(res); + continue; + } + chunk = PQgetvalue(res, 0, 2); pg_log(PG_DEBUG, "received chunk for file \"%s\", off %d, len %d\n", @@ -445,7 +464,7 @@ libpq_executeFileMap(filemap_t *map) */ sql = "SELECT path, begin, \n" - " pg_read_binary_file(path, begin, len) AS chunk\n" + " pg_read_binary_file(path, begin, len, true) AS chunk\n" "FROM fetchchunks\n"; receiveFileChunks(sql); -- 2.4.4
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c index 7e54ac5..ea5385c 100644 --- a/src/bin/pg_rewind/pg_rewind.c +++ b/src/bin/pg_rewind/pg_rewind.c @@ -291,6 +291,12 @@ main(int argc, char **argv) print_filemap(); /* + * Sleep here to allow user to do nasty things with the source file + * list. + */ + pg_usleep(10000000); /* 10s */ + + /* * Ok, we're ready to start copying things over. */ if (showprogress)
rewind_test.bash
Description: Binary data
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers