Add pg_file_sync() to adminpack extension.
authorFujii Masao <fujii@postgresql.org>
Fri, 24 Jan 2020 11:42:52 +0000 (20:42 +0900)
committerFujii Masao <fujii@postgresql.org>
Fri, 24 Jan 2020 11:42:52 +0000 (20:42 +0900)
This function allows us to fsync the specified file or directory.
It's useful, for example, when we want to sync the file that
pg_file_write() writes out or that COPY TO exports the data into,
for durability.

Author: Fujii Masao
Reviewed-By: Julien Rouhaud, Arthur Zakirov, Michael Paquier, Atsushi Torikoshi
Discussion: https://www.postgresql.org/message-id/CAHGQGwGY8uzZ_k8dHRoW1zDcy1Z7=5GQ+So4ZkVy2u=nLsk=hA@mail.gmail.com

contrib/adminpack/Makefile
contrib/adminpack/adminpack--2.0--2.1.sql [new file with mode: 0644]
contrib/adminpack/adminpack.c
contrib/adminpack/adminpack.control
contrib/adminpack/expected/adminpack.out
contrib/adminpack/sql/adminpack.sql
doc/src/sgml/adminpack.sgml
src/backend/storage/file/fd.c
src/include/storage/fd.h

index 9a464b11bc96ad1facc9bf5d9e270021a2ede5b8..630fea7726c7b06bb29a9205376b9c8590a159d9 100644 (file)
@@ -7,7 +7,8 @@ OBJS = \
 PG_CPPFLAGS = -I$(libpq_srcdir)
 
 EXTENSION = adminpack
-DATA = adminpack--1.0.sql adminpack--1.0--1.1.sql adminpack--1.1--2.0.sql
+DATA = adminpack--1.0.sql adminpack--1.0--1.1.sql adminpack--1.1--2.0.sql\
+       adminpack--2.0--2.1.sql
 PGFILEDESC = "adminpack - support functions for pgAdmin"
 
 REGRESS = adminpack
diff --git a/contrib/adminpack/adminpack--2.0--2.1.sql b/contrib/adminpack/adminpack--2.0--2.1.sql
new file mode 100644 (file)
index 0000000..1c6712e
--- /dev/null
@@ -0,0 +1,17 @@
+/* contrib/adminpack/adminpack--2.0--2.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION adminpack UPDATE TO '2.1'" to load this file. \quit
+
+/* ***********************************************
+ * Administrative functions for PostgreSQL
+ * *********************************************** */
+
+/* generic file access functions */
+
+CREATE OR REPLACE FUNCTION pg_catalog.pg_file_sync(text)
+RETURNS void
+AS 'MODULE_PATHNAME', 'pg_file_sync'
+LANGUAGE C VOLATILE STRICT;
+
+REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_sync(text) FROM PUBLIC;
index 710f4ea32d69b93df9464eb917aa0377105448ac..7b5a531e080892ed97a58c531afccce2ebb91866 100644 (file)
@@ -43,6 +43,7 @@ PG_MODULE_MAGIC;
 
 PG_FUNCTION_INFO_V1(pg_file_write);
 PG_FUNCTION_INFO_V1(pg_file_write_v1_1);
+PG_FUNCTION_INFO_V1(pg_file_sync);
 PG_FUNCTION_INFO_V1(pg_file_rename);
 PG_FUNCTION_INFO_V1(pg_file_rename_v1_1);
 PG_FUNCTION_INFO_V1(pg_file_unlink);
@@ -215,6 +216,30 @@ pg_file_write_internal(text *file, text *data, bool replace)
        return (count);
 }
 
+/* ------------------------------------
+ * pg_file_sync
+ *
+ * We REVOKE EXECUTE on the function from PUBLIC.
+ * Users can then grant access to it based on their policies.
+ */
+Datum
+pg_file_sync(PG_FUNCTION_ARGS)
+{
+       char       *filename;
+       struct stat     fst;
+
+       filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false);
+
+       if (stat(filename, &fst) < 0)
+               ereport(ERROR,
+                               (errcode_for_file_access(),
+                                errmsg("could not stat file \"%s\": %m", filename)));
+
+       fsync_fname_ext(filename, S_ISDIR(fst.st_mode), false, ERROR);
+
+       PG_RETURN_VOID();
+}
+
 /* ------------------------------------
  * pg_file_rename - old version
  *
index 12569dcdd71a738a052fca6439e9f868ae899eb8..ae35d22156aa65e3848309fd4a41012036033ca2 100644 (file)
@@ -1,6 +1,6 @@
 # adminpack extension
 comment = 'administrative functions for PostgreSQL'
-default_version = '2.0'
+default_version = '2.1'
 module_pathname = '$libdir/adminpack'
 relocatable = false
 schema = pg_catalog
index 8747ac69a27b51587c29e33a6ee09f398c86ba9a..5738b0f6c4dd071f829055cf679583b44f6f2f70 100644 (file)
@@ -56,6 +56,21 @@ RESET ROLE;
 REVOKE EXECUTE ON FUNCTION pg_file_write(text,text,bool) FROM regress_user1;
 REVOKE pg_read_all_settings FROM regress_user1;
 DROP ROLE regress_user1;
+-- sync
+SELECT pg_file_sync('test_file1'); -- sync file
+ pg_file_sync 
+--------------
+(1 row)
+
+SELECT pg_file_sync('pg_stat'); -- sync directory
+ pg_file_sync 
+--------------
+(1 row)
+
+SELECT pg_file_sync('test_file2'); -- not there
+ERROR:  could not stat file "test_file2": No such file or directory
 -- rename file
 SELECT pg_file_rename('test_file1', 'test_file2');
  pg_file_rename 
@@ -142,6 +157,8 @@ CREATE USER regress_user1;
 SET ROLE regress_user1;
 SELECT pg_file_write('test_file0', 'test0', false);
 ERROR:  permission denied for function pg_file_write
+SELECT pg_file_sync('test_file0');
+ERROR:  permission denied for function pg_file_sync
 SELECT pg_file_rename('test_file0', 'test_file0');
 ERROR:  permission denied for function pg_file_rename
 CONTEXT:  SQL function "pg_file_rename" statement 1
index 1525f0a82bd84b6f2565c4a3963c9606a3f0a10e..918d0bdc65ee988ded7757b1a25cacbf387feb5e 100644 (file)
@@ -29,6 +29,11 @@ REVOKE EXECUTE ON FUNCTION pg_file_write(text,text,bool) FROM regress_user1;
 REVOKE pg_read_all_settings FROM regress_user1;
 DROP ROLE regress_user1;
 
+-- sync
+SELECT pg_file_sync('test_file1'); -- sync file
+SELECT pg_file_sync('pg_stat'); -- sync directory
+SELECT pg_file_sync('test_file2'); -- not there
+
 -- rename file
 SELECT pg_file_rename('test_file1', 'test_file2');
 SELECT pg_read_file('test_file1');  -- not there
@@ -58,6 +63,7 @@ CREATE USER regress_user1;
 SET ROLE regress_user1;
 
 SELECT pg_file_write('test_file0', 'test0', false);
+SELECT pg_file_sync('test_file0');
 SELECT pg_file_rename('test_file0', 'test_file0');
 SELECT pg_file_unlink('test_file0');
 SELECT pg_logdir_ls();
index 2655417366c184fe0812a170fb5814600278907e..977073f7c8dd2cbb7c9cc857fd5758ff2cd347c1 100644 (file)
       Write, or append to, a text file
      </entry>
     </row>
+    <row>
+     <entry><function>pg_catalog.pg_file_sync(filename text)</function></entry>
+     <entry><type>void</type></entry>
+     <entry>
+      Flush a file or directory to disk
+     </entry>
+    </row>
     <row>
      <entry><function>pg_catalog.pg_file_rename(oldname text, newname text <optional>, archivename text</optional>)</function></entry>
      <entry><type>boolean</type></entry>
   Returns the number of bytes written.
  </para>
 
+ <indexterm>
+  <primary>pg_file_sync</primary>
+ </indexterm>
+ <para>
+  <function>pg_file_sync</function> fsyncs the specified file or directory
+  named by <parameter>filename</parameter>.  An error is thrown
+  on failure (e.g., the specified file is not present). Note that
+  <xref linkend="guc-data-sync-retry"/> has no effect on this function,
+  and therefore a PANIC-level error will not be raised even on failure to
+  flush database files.
+ </para>
+
  <indexterm>
   <primary>pg_file_rename</primary>
  </indexterm>
index fa79b45f631b182f4c0274f279996b57eeeacd4b..b5f4df6a4855172c6695006823a8f572a394bff1 100644 (file)
@@ -319,7 +319,6 @@ static void pre_sync_fname(const char *fname, bool isdir, int elevel);
 static void datadir_fsync_fname(const char *fname, bool isdir, int elevel);
 static void unlink_if_exists_fname(const char *fname, bool isdir, int elevel);
 
-static int     fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
 static int     fsync_parent_path(const char *fname, int elevel);
 
 
@@ -3376,7 +3375,7 @@ unlink_if_exists_fname(const char *fname, bool isdir, int elevel)
  *
  * Returns 0 if the operation succeeded, -1 otherwise.
  */
-static int
+int
 fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel)
 {
        int                     fd;
index c6ce7eacf2a8ccd5943b08bc7165307ae35e60e7..51e2ece3c9df6b96817500147d9059c1d22250ac 100644 (file)
@@ -145,6 +145,7 @@ extern int  pg_fsync_writethrough(int fd);
 extern int     pg_fdatasync(int fd);
 extern void pg_flush_data(int fd, off_t offset, off_t amount);
 extern void fsync_fname(const char *fname, bool isdir);
+extern int     fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
 extern int     durable_rename(const char *oldfile, const char *newfile, int loglevel);
 extern int     durable_unlink(const char *fname, int loglevel);
 extern int     durable_link_or_rename(const char *oldfile, const char *newfile, int loglevel);