On 3/29/18 11:04 PM, Michael Paquier wrote:
> On Thu, Mar 29, 2018 at 10:59:59AM -0400, David Steele wrote:
>> On 3/28/18 1:59 AM, Michael Paquier wrote:
>>
>>> In pg_backup_tar.c, we still have a 0600 hardcoded.  You should use
>>> PG_FILE_MODE_DEFAULT there as well.
>>
>> I'm starting to wonder if these changes in pg_dump make sense.  The
>> file/tar permissions here do not map directly to anything in the PGDATA
>> directory (since the dump and restore are logical).  Perhaps we should
>> be adding a -g option for pg_dump (in a separate patch) if we want this
>> functionality?
> 
> Yeah.  I am having second thoughts on this one actually.  pg_dump
> handles logical backups which require just a connection to Postgres and
> it does not care about the physical state of the relation files.  So I
> am dropping my comment, and let's not bother about changing things
> here.

Glad you agree.  I have reverted the mode changes in pg_dump.

>>> dsm_impl_posix() can create a new segment with shm_open using as mode
>>> 0600.  This is present in pg_dynshmem, which would be included in
>>> backups.  First, it seems to me that this should use
>>> PG_FILE_MODE_DEFAULT.  Then, with patch 2 it seems to me that if an
>>> instance is using DSM then there is a risk of breaking a simple backup
>>> which uses for example "tar" without --exclude filters with group access
>>> (sometimes scripts are not smart enough to skip the same contents as
>>> base backups).  So it seems to me that DSM should be also made more
>>> aware of group access to ease the life of users.
>>
>> Done in patch 1.  For patch 2, do you see any issues with the shared
>> memory being group readable from a security perspective?  The user can
>> read everything on disk so it's hard to see how it's a problem.  Also,
>> if these files are ending up in people's backups...
> 
> They would be nuked from the surface of earth when recovery kicks.
> People should filter this folder, which is why any popular Postgres
> backup tool I believe does so, and now so do both pg_rewind and
> pg_basebackup.  Still if a user is able to read everything, being able
> to read as well pg_dynshmem does not change much from a security's point
> of view.  So consistency makes the most sense to me.

Done.

>>> +/*
>>> + * Does the data directory allow group read access?  The default is false, 
>>> i.e.
>>> + * mode 0700, but may be changed in checkDataDir() to true, i.e. mode 0750.
>>> + */
>>> +bool DataDirGroupAccess = false;
>>>
>>> Instead of a boolean, I would suggest to use an integer, this is more
>>> consistent with log_file_mode.  
>>
>> Well, the goal was to make this implementation independent, but I'm not
>> against the idea.  Anybody have a preference?
> 
> You mean Windows here?  Perhaps you are right and just using a boolean
> would be fine.  When commenting on that I have been likely wondering
> about potential extensions in the future, like allowing a user to write
> files in a data folder as well if member of the same group as the user
> managing the Postgres intance, like for backup deployment?  Perhaps
> that's just a crazy man's thoughts..

Haha!  But I think you are right that using the mode is more consistent
with how other GUCs are done.  It hardly makes sense to take a stand on
unix-y things here when we use them in other GUCs already.

I have replaced data_directory_group_access with data_directory_mode.

>>> Actually, about that, should we complain
>>> if log_file_mode is set to a value incompatible?
>>
>> I think control of the log file mode should be independent.  I usually
>> don't store log files in PGDATA at all.  What if we set log_file_mode
>> based on the -g option passed to initdb?  That will work well for
>> default installations while providing flexibility to others.
> 
> Let's do nothing here as well.  This will keep the code more simple, and
> normally-sane deployments put the log directory in an absolute path out
> of the data folder.  Normally...  So it means that if I create a number
> out of thin air I would imagine that less than 20% of deployments are
> like that.

I decided this made sense to do.  It was only a few lines in initdb.c
using a very well established pattern.  It would be surprising if log
files did not follow the mode of the rest of PGDATA after initdb -g,
even if it is standard practice to relocate them.

Thanks,
-- 
-David
da...@pgmasters.net
From b48aef8b82742683017bbb7b8ac2f509ed914414 Mon Sep 17 00:00:00 2001
From: David Steele <da...@pgmasters.net>
Date: Fri, 30 Mar 2018 13:22:01 -0400
Subject: [PATCH 1/1] Refactor file permissions in backend/frontend

Adds a new header (file_perm.h) and makes all front-end utilities use the new 
constants for setting umask. Converts mkdir() calls in the backend to 
MakeDirectoryDefaultPerm() if the original call used default permissions. Adds 
tests to make sure permissions in PGDATA are set correctly by the front-end 
tools.
---
 src/backend/access/transam/xlog.c            |  2 +-
 src/backend/commands/tablespace.c            | 18 ++++---
 src/backend/postmaster/postmaster.c          |  5 +-
 src/backend/postmaster/syslogger.c           |  5 +-
 src/backend/replication/basebackup.c         |  5 +-
 src/backend/replication/slot.c               |  5 +-
 src/backend/storage/file/copydir.c           |  2 +-
 src/backend/storage/file/fd.c                | 32 +++++++-----
 src/backend/storage/ipc/dsm_impl.c           |  3 +-
 src/backend/storage/ipc/ipc.c                |  4 ++
 src/backend/utils/init/miscinit.c            |  5 +-
 src/bin/initdb/initdb.c                      | 24 ++++-----
 src/bin/initdb/t/001_initdb.pl               |  4 +-
 src/bin/pg_basebackup/pg_basebackup.c        |  7 +--
 src/bin/pg_basebackup/t/010_pg_basebackup.pl |  9 +++-
 src/bin/pg_basebackup/t/020_pg_receivewal.pl |  9 +++-
 src/bin/pg_basebackup/walmethods.c           |  6 ++-
 src/bin/pg_ctl/pg_ctl.c                      |  4 +-
 src/bin/pg_ctl/t/001_start_stop.pl           | 13 +++--
 src/bin/pg_resetwal/pg_resetwal.c            |  8 ++-
 src/bin/pg_resetwal/t/001_basic.pl           |  5 +-
 src/bin/pg_rewind/RewindTest.pm              |  4 ++
 src/bin/pg_rewind/file_ops.c                 |  7 +--
 src/bin/pg_rewind/pg_rewind.c                |  4 ++
 src/bin/pg_rewind/t/001_basic.pl             |  3 +-
 src/bin/pg_upgrade/file.c                    |  5 +-
 src/bin/pg_upgrade/pg_upgrade.c              |  3 +-
 src/bin/pg_upgrade/test.sh                   | 11 +++++
 src/include/common/file_perm.h               | 32 ++++++++++++
 src/include/storage/fd.h                     |  3 ++
 src/test/perl/PostgresNode.pm                |  3 ++
 src/test/perl/TestLib.pm                     | 73 ++++++++++++++++++++++++++++
 32 files changed, 256 insertions(+), 67 deletions(-)
 create mode 100644 src/include/common/file_perm.h

diff --git a/src/backend/access/transam/xlog.c 
b/src/backend/access/transam/xlog.c
index 0bca449eac..0a0ab1aff9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4086,7 +4086,7 @@ ValidateXLOGDirectoryStructure(void)
        {
                ereport(LOG,
                                (errmsg("creating missing WAL directory 
\"%s\"", path)));
-               if (mkdir(path, S_IRWXU) < 0)
+               if (MakeDirectoryDefaultPerm(path) < 0)
                        ereport(FATAL,
                                        (errmsg("could not create missing 
directory \"%s\": %m",
                                                        path)));
diff --git a/src/backend/commands/tablespace.c 
b/src/backend/commands/tablespace.c
index 5c450caa4e..f7e1818350 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -68,6 +68,7 @@
 #include "commands/seclabel.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "common/file_perm.h"
 #include "miscadmin.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
@@ -151,7 +152,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool 
isRedo)
                        else
                        {
                                /* Directory creation failed? */
-                               if (mkdir(dir, S_IRWXU) < 0)
+                               if (MakeDirectoryDefaultPerm(dir) < 0)
                                {
                                        char       *parentdir;
 
@@ -173,7 +174,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool 
isRedo)
                                        get_parent_directory(parentdir);
                                        get_parent_directory(parentdir);
                                        /* Can't create parent and it doesn't 
already exist? */
-                                       if (mkdir(parentdir, S_IRWXU) < 0 && 
errno != EEXIST)
+                                       if (MakeDirectoryDefaultPerm(parentdir) 
< 0 && errno != EEXIST)
                                                ereport(ERROR,
                                                                
(errcode_for_file_access(),
                                                                 errmsg("could 
not create directory \"%s\": %m",
@@ -184,7 +185,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool 
isRedo)
                                        parentdir = pstrdup(dir);
                                        get_parent_directory(parentdir);
                                        /* Can't create parent and it doesn't 
already exist? */
-                                       if (mkdir(parentdir, S_IRWXU) < 0 && 
errno != EEXIST)
+                                       if (MakeDirectoryDefaultPerm(parentdir) 
< 0 && errno != EEXIST)
                                                ereport(ERROR,
                                                                
(errcode_for_file_access(),
                                                                 errmsg("could 
not create directory \"%s\": %m",
@@ -192,7 +193,7 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool 
isRedo)
                                        pfree(parentdir);
 
                                        /* Create database directory */
-                                       if (mkdir(dir, S_IRWXU) < 0)
+                                       if (MakeDirectoryDefaultPerm(dir) < 0)
                                                ereport(ERROR,
                                                                
(errcode_for_file_access(),
                                                                 errmsg("could 
not create directory \"%s\": %m",
@@ -279,7 +280,8 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
        /*
         * Check that location isn't too long. Remember that we're going to 
append
         * 'PG_XXX/<dboid>/<relid>_<fork>.<nnn>'.  FYI, we never actually
-        * reference the whole path here, but mkdir() uses the first two parts.
+        * reference the whole path here, but MakeDirectoryDefaultPerm() uses 
the first
+        * two parts.
         */
        if (strlen(location) + 1 + strlen(TABLESPACE_VERSION_DIRECTORY) + 1 +
                OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1 + OIDCHARS > 
MAXPGPATH)
@@ -574,7 +576,7 @@ create_tablespace_directories(const char *location, const 
Oid tablespaceoid)
         * Attempt to coerce target directory to safe permissions.  If this 
fails,
         * it doesn't exist or has the wrong owner.
         */
-       if (chmod(location, S_IRWXU) != 0)
+       if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
        {
                if (errno == ENOENT)
                        ereport(ERROR,
@@ -599,7 +601,7 @@ create_tablespace_directories(const char *location, const 
Oid tablespaceoid)
                if (stat(location_with_version_dir, &st) == 0 && 
S_ISDIR(st.st_mode))
                {
                        if (!rmtree(location_with_version_dir, true))
-                               /* If this failed, mkdir() below is going to 
error. */
+                               /* If this failed, MakeDirectoryDefaultPerm() 
below is going to error. */
                                ereport(WARNING,
                                                (errmsg("some useless files may 
be left behind in old database directory \"%s\"",
                                                                
location_with_version_dir)));
@@ -610,7 +612,7 @@ create_tablespace_directories(const char *location, const 
Oid tablespaceoid)
         * The creation of the version directory prevents more than one 
tablespace
         * in a single location.
         */
-       if (mkdir(location_with_version_dir, S_IRWXU) < 0)
+       if (MakeDirectoryDefaultPerm(location_with_version_dir) < 0)
        {
                if (errno == EEXIST)
                        ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c 
b/src/backend/postmaster/postmaster.c
index 660f3185e6..868fba8cac 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -97,6 +97,7 @@
 #include "access/xlog.h"
 #include "bootstrap/bootstrap.h"
 #include "catalog/pg_control.h"
+#include "common/file_perm.h"
 #include "common/ip.h"
 #include "lib/ilist.h"
 #include "libpq/auth.h"
@@ -589,7 +590,7 @@ PostmasterMain(int argc, char *argv[])
        /*
         * for security, no dir or file created can be group or other accessible
         */
-       umask(S_IRWXG | S_IRWXO);
+       umask(PG_MODE_MASK_DEFAULT);
 
        /*
         * Initialize random(3) so we don't get the same values in every run.
@@ -4492,7 +4493,7 @@ internal_forkexec(int argc, char *argv[], Port *port)
                 * As in OpenTemporaryFileInTablespace, try to make the 
temp-file
                 * directory
                 */
-               mkdir(PG_TEMP_FILES_DIR, S_IRWXU);
+               MakeDirectoryDefaultPerm(PG_TEMP_FILES_DIR);
 
                fp = AllocateFile(tmpfilename, PG_BINARY_W);
                if (!fp)
diff --git a/src/backend/postmaster/syslogger.c 
b/src/backend/postmaster/syslogger.c
index f70eea37df..ae23941f59 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -41,6 +41,7 @@
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
+#include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/pg_shmem.h"
@@ -322,7 +323,7 @@ SysLoggerMain(int argc, char *argv[])
                                /*
                                 * Also, create new directory if not present; 
ignore errors
                                 */
-                               mkdir(Log_directory, S_IRWXU);
+                               MakeDirectoryDefaultPerm(Log_directory);
                        }
                        if (strcmp(Log_filename, currentLogFilename) != 0)
                        {
@@ -564,7 +565,7 @@ SysLogger_Start(void)
        /*
         * Create log directory if not present; ignore errors
         */
-       mkdir(Log_directory, S_IRWXU);
+       MakeDirectoryDefaultPerm(Log_directory);
 
        /*
         * The initial logfile is created right in the postmaster, to verify 
that
diff --git a/src/backend/replication/basebackup.c 
b/src/backend/replication/basebackup.c
index 516eea57f8..97e2bdfc73 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -19,6 +19,7 @@
 #include "access/xlog_internal.h"      /* for pg_start/stop_backup */
 #include "catalog/catalog.h"
 #include "catalog/pg_type.h"
+#include "common/file_perm.h"
 #include "lib/stringinfo.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -878,7 +879,7 @@ sendFileWithContent(const char *filename, const char 
*content)
        statbuf.st_gid = getegid();
 #endif
        statbuf.st_mtime = time(NULL);
-       statbuf.st_mode = S_IRUSR | S_IWUSR;
+       statbuf.st_mode = PG_FILE_MODE_DEFAULT;
        statbuf.st_size = len;
 
        _tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1397,7 +1398,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct 
stat *statbuf,
 #else
        if (pgwin32_is_junction(pathbuf))
 #endif
-               statbuf->st_mode = S_IFDIR | S_IRWXU;
+               statbuf->st_mode = S_IFDIR | PG_DIR_MODE_DEFAULT;
 
        return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, 
sizeonly);
 }
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fc9ef22b0b..3a98360b50 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1166,13 +1166,14 @@ CreateSlotOnDisk(ReplicationSlot *slot)
         * It's just barely possible that some previous effort to create or 
drop a
         * slot with this name left a temp directory lying around. If that seems
         * to be the case, try to remove it.  If the rmtree() fails, we'll error
-        * out at the mkdir() below, so we don't bother checking success.
+        * out at the MakeDirectoryDefaultPerm() below, so we don't bother 
checking
+        * success.
         */
        if (stat(tmppath, &st) == 0 && S_ISDIR(st.st_mode))
                rmtree(tmppath, true);
 
        /* Create and fsync the temporary slot directory. */
-       if (mkdir(tmppath, S_IRWXU) < 0)
+       if (MakeDirectoryDefaultPerm(tmppath) < 0)
                ereport(ERROR,
                                (errcode_for_file_access(),
                                 errmsg("could not create directory \"%s\": %m",
diff --git a/src/backend/storage/file/copydir.c 
b/src/backend/storage/file/copydir.c
index ca6342db0d..ff980dc1f5 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -41,7 +41,7 @@ copydir(char *fromdir, char *todir, bool recurse)
        char            fromfile[MAXPGPATH * 2];
        char            tofile[MAXPGPATH * 2];
 
-       if (mkdir(todir, S_IRWXU) != 0)
+       if (MakeDirectoryDefaultPerm(todir) != 0)
                ereport(ERROR,
                                (errcode_for_file_access(),
                                 errmsg("could not create directory \"%s\": 
%m", todir)));
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index d30a725f90..40f754afb6 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -84,6 +84,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/pg_tablespace.h"
+#include "common/file_perm.h"
 #include "pgstat.h"
 #include "portability/mem.h"
 #include "storage/fd.h"
@@ -124,12 +125,6 @@
  */
 #define FD_MINFREE                             10
 
-/*
- * Default mode for created files, unless something else is specified using
- * the *Perm() function variants.
- */
-#define PG_FILE_MODE_DEFAULT   (S_IRUSR | S_IWUSR)
-
 /*
  * A number of platforms allow individual processes to open many more files
  * than they can really support when *many* processes do the same thing.
@@ -1434,7 +1429,7 @@ PathNameOpenFilePerm(const char *fileName, int fileFlags, 
mode_t fileMode)
 void
 PathNameCreateTemporaryDir(const char *basedir, const char *directory)
 {
-       if (mkdir(directory, S_IRWXU) < 0)
+       if (MakeDirectoryDefaultPerm(directory) < 0)
        {
                if (errno == EEXIST)
                        return;
@@ -1444,14 +1439,14 @@ PathNameCreateTemporaryDir(const char *basedir, const 
char *directory)
                 * EEXIST to close a race against another process following the 
same
                 * algorithm.
                 */
-               if (mkdir(basedir, S_IRWXU) < 0 && errno != EEXIST)
+               if (MakeDirectoryDefaultPerm(basedir) < 0 && errno != EEXIST)
                        ereport(ERROR,
                                        (errcode_for_file_access(),
                                         errmsg("cannot create temporary 
directory \"%s\": %m",
                                                        basedir)));
 
                /* Try again. */
-               if (mkdir(directory, S_IRWXU) < 0 && errno != EEXIST)
+               if (MakeDirectoryDefaultPerm(directory) < 0 && errno != EEXIST)
                        ereport(ERROR,
                                        (errcode_for_file_access(),
                                         errmsg("cannot create temporary 
subdirectory \"%s\": %m",
@@ -1601,11 +1596,11 @@ OpenTemporaryFileInTablespace(Oid tblspcOid, bool 
rejectError)
                 * We might need to create the tablespace's tempfile directory, 
if no
                 * one has yet done so.
                 *
-                * Don't check for error from mkdir; it could fail if someone 
else
-                * just did the same thing.  If it doesn't work then we'll bomb 
out on
+                * Don't check error from MakeDirectoryDefaultPerm; it could 
fail if someone
+                * else just did the same thing.  If it doesn't work then we'll 
bomb out on
                 * the second create attempt, instead.
                 */
-               mkdir(tempdirpath, S_IRWXU);
+               MakeDirectoryDefaultPerm(tempdirpath);
 
                file = PathNameOpenFile(tempfilepath,
                                                                O_RDWR | 
O_CREAT | O_TRUNC | PG_BINARY);
@@ -3554,3 +3549,16 @@ fsync_parent_path(const char *fname, int elevel)
 
        return 0;
 }
+
+/*
+ * Create a directory using PG_DIR_MODE_DEFAULT for permissions
+ *
+ * Directories in PGDATA should normally have specific permissions -- when
+ * using this function the caller does not need to know what they should be.
+ * For permissions other than the default use mkdir() directly.
+ */
+int
+MakeDirectoryDefaultPerm(const char *directoryName)
+{
+       return mkdir(directoryName, PG_DIR_MODE_DEFAULT);
+}
diff --git a/src/backend/storage/ipc/dsm_impl.c 
b/src/backend/storage/ipc/dsm_impl.c
index 67e76b98fe..0ba45b4f93 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -60,6 +60,7 @@
 #ifdef HAVE_SYS_SHM_H
 #include <sys/shm.h>
 #endif
+#include "common/file_perm.h"
 #include "pgstat.h"
 
 #include "portability/mem.h"
@@ -285,7 +286,7 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size 
request_size,
         * returning.
         */
        flags = O_RDWR | (op == DSM_OP_CREATE ? O_CREAT | O_EXCL : 0);
-       if ((fd = shm_open(name, flags, 0600)) == -1)
+       if ((fd = shm_open(name, flags, PG_FILE_MODE_DEFAULT)) == -1)
        {
                if (errno != EEXIST)
                        ereport(elevel,
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 726db7b7f1..466613e4af 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -137,6 +137,10 @@ proc_exit(int code)
                else
                        snprintf(gprofDirName, 32, "gprof/%d", (int) getpid());
 
+               /*
+                * Use mkdir() instead of MakeDirectoryDefaultPerm() here 
because non-default
+                * permissions are required.
+                */
                mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
                mkdir(gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
                chdir(gprofDirName);
diff --git a/src/backend/utils/init/miscinit.c 
b/src/backend/utils/init/miscinit.c
index 87ed7d3f71..82da4b9f27 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,6 +32,7 @@
 
 #include "access/htup_details.h"
 #include "catalog/pg_authid.h"
+#include "common/file_perm.h"
 #include "libpq/libpq.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -831,7 +832,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
                 * Think not to make the file protection weaker than 0600.  See
                 * comments below.
                 */
-               fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
+               fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 
PG_FILE_MODE_DEFAULT);
                if (fd >= 0)
                        break;                          /* Success; exit the 
retry loop */
 
@@ -848,7 +849,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
                 * Read the file to get the old owner's PID.  Note race 
condition
                 * here: file might have been deleted since we tried to create 
it.
                 */
-               fd = open(filename, O_RDONLY, 0600);
+               fd = open(filename, O_RDONLY, PG_FILE_MODE_DEFAULT);
                if (fd < 0)
                {
                        if (errno == ENOENT)
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 78990f5a27..0b69eded81 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -64,6 +64,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_collation.h"
+#include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/restricted_token.h"
 #include "common/username.h"
@@ -1170,7 +1171,7 @@ setup_config(void)
        snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
 
        writefile(path, conflines);
-       if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -1190,7 +1191,7 @@ setup_config(void)
        sprintf(path, "%s/postgresql.auto.conf", pg_data);
 
        writefile(path, autoconflines);
-       if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -1277,7 +1278,7 @@ setup_config(void)
        snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
 
        writefile(path, conflines);
-       if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -1293,7 +1294,7 @@ setup_config(void)
        snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
 
        writefile(path, conflines);
-       if (chmod(path, S_IRUSR | S_IWUSR) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -2692,7 +2693,7 @@ create_data_directory(void)
                                   pg_data);
                        fflush(stdout);
 
-                       if (pg_mkdir_p(pg_data, S_IRWXU) != 0)
+                       if (pg_mkdir_p(pg_data, PG_DIR_MODE_DEFAULT) != 0)
                        {
                                fprintf(stderr, _("%s: could not create 
directory \"%s\": %s\n"),
                                                progname, pg_data, 
strerror(errno));
@@ -2710,7 +2711,7 @@ create_data_directory(void)
                                   pg_data);
                        fflush(stdout);
 
-                       if (chmod(pg_data, S_IRWXU) != 0)
+                       if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
                        {
                                fprintf(stderr, _("%s: could not change 
permissions of directory \"%s\": %s\n"),
                                                progname, pg_data, 
strerror(errno));
@@ -2778,7 +2779,7 @@ create_xlog_or_symlink(void)
                                           xlog_dir);
                                fflush(stdout);
 
-                               if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0)
+                               if (pg_mkdir_p(xlog_dir, PG_DIR_MODE_DEFAULT) 
!= 0)
                                {
                                        fprintf(stderr, _("%s: could not create 
directory \"%s\": %s\n"),
                                                        progname, xlog_dir, 
strerror(errno));
@@ -2796,7 +2797,7 @@ create_xlog_or_symlink(void)
                                           xlog_dir);
                                fflush(stdout);
 
-                               if (chmod(xlog_dir, S_IRWXU) != 0)
+                               if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
                                {
                                        fprintf(stderr, _("%s: could not change 
permissions of directory \"%s\": %s\n"),
                                                        progname, xlog_dir, 
strerror(errno));
@@ -2846,7 +2847,7 @@ create_xlog_or_symlink(void)
        else
        {
                /* Without -X option, just make the subdirectory normally */
-               if (mkdir(subdirloc, S_IRWXU) < 0)
+               if (mkdir(subdirloc, PG_DIR_MODE_DEFAULT) < 0)
                {
                        fprintf(stderr, _("%s: could not create directory 
\"%s\": %s\n"),
                                        progname, subdirloc, strerror(errno));
@@ -2882,7 +2883,8 @@ initialize_data_directory(void)
 
        setup_signals();
 
-       umask(S_IRWXG | S_IRWXO);
+       /* Set dir/file mode mask */
+       umask(PG_MODE_MASK_DEFAULT);
 
        create_data_directory();
 
@@ -2902,7 +2904,7 @@ initialize_data_directory(void)
                 * The parent directory already exists, so we only need mkdir() 
not
                 * pg_mkdir_p() here, which avoids some failure modes; cf bug 
#13853.
                 */
-               if (mkdir(path, S_IRWXU) < 0)
+               if (mkdir(path, PG_DIR_MODE_DEFAULT) < 0)
                {
                        fprintf(stderr, _("%s: could not create directory 
\"%s\": %s\n"),
                                        progname, path, strerror(errno));
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index c0cfa6e92c..3331560445 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -6,7 +6,7 @@ use strict;
 use warnings;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 15;
+use Test::More tests => 16;
 
 my $tempdir = TestLib::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
@@ -45,6 +45,8 @@ mkdir $datadir;
 
        command_ok([ 'initdb', '-N', '-T', 'german', '-X', $xlogdir, $datadir ],
                'successful creation');
+
+       ok(check_mode_recursive($datadir, 0700, 0600));
 }
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
diff --git a/src/bin/pg_basebackup/pg_basebackup.c 
b/src/bin/pg_basebackup/pg_basebackup.c
index 1b32592063..3d6e47008a 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -27,6 +27,7 @@
 #endif
 
 #include "access/xlog_internal.h"
+#include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/string.h"
 #include "fe_utils/string_utils.h"
@@ -624,7 +625,7 @@ StartLogStreamer(char *startpos, uint32 timeline, char 
*sysidentifier)
                                 PQserverVersion(conn) < 
MINIMUM_VERSION_FOR_PG_WAL ?
                                 "pg_xlog" : "pg_wal");
 
-               if (pg_mkdir_p(statusdir, S_IRWXU) != 0 && errno != EEXIST)
+               if (pg_mkdir_p(statusdir, PG_DIR_MODE_DEFAULT) != 0 && errno != 
EEXIST)
                {
                        fprintf(stderr,
                                        _("%s: could not create directory 
\"%s\": %s\n"),
@@ -680,7 +681,7 @@ verify_dir_is_empty_or_create(char *dirname, bool *created, 
bool *found)
                        /*
                         * Does not exist, so create
                         */
-                       if (pg_mkdir_p(dirname, S_IRWXU) == -1)
+                       if (pg_mkdir_p(dirname, PG_DIR_MODE_DEFAULT) == -1)
                        {
                                fprintf(stderr,
                                                _("%s: could not create 
directory \"%s\": %s\n"),
@@ -1436,7 +1437,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int 
rownum)
                                         * Directory
                                         */
                                        filename[strlen(filename) - 1] = '\0';  
/* Remove trailing slash */
-                                       if (mkdir(filename, S_IRWXU) != 0)
+                                       if (mkdir(filename, 
PG_DIR_MODE_DEFAULT) != 0)
                                        {
                                                /*
                                                 * When streaming WAL, pg_wal 
(or pg_xlog for pre-9.6
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl 
b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 32d21ce644..8d9f9c21f4 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
 use File::Basename qw(basename dirname);
 use PostgresNode;
 use TestLib;
-use Test::More tests => 93;
+use Test::More tests => 94;
 
 program_help_ok('pg_basebackup');
 program_version_ok('pg_basebackup');
@@ -15,6 +15,9 @@ my $tempdir = TestLib::tempdir;
 
 my $node = get_new_node('main');
 
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
 # Initialize node without replication settings
 $node->init;
 $node->start;
@@ -93,6 +96,10 @@ $node->command_ok([ 'pg_basebackup', '-D', 
"$tempdir/backup", '-X', 'none' ],
        'pg_basebackup runs');
 ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
 
+# Group access should not be enabled on backup
+ok(check_mode_recursive("$tempdir/backup", 0700, 0600),
+   "check backup dir permissions");
+
 # Only archive_status directory should be copied in pg_wal/.
 is_deeply(
        [ sort(slurp_dir("$tempdir/backup/pg_wal/")) ],
diff --git a/src/bin/pg_basebackup/t/020_pg_receivewal.pl 
b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
index 64e3a35a87..d155d5b711 100644
--- a/src/bin/pg_basebackup/t/020_pg_receivewal.pl
+++ b/src/bin/pg_basebackup/t/020_pg_receivewal.pl
@@ -2,12 +2,15 @@ use strict;
 use warnings;
 use TestLib;
 use PostgresNode;
-use Test::More tests => 18;
+use Test::More tests => 19;
 
 program_help_ok('pg_receivewal');
 program_version_ok('pg_receivewal');
 program_options_handling_ok('pg_receivewal');
 
+# Set umask so no test directories or files are created with group permissions
+umask (0077);
+
 my $primary = get_new_node('primary');
 $primary->init(allows_streaming => 1);
 $primary->start;
@@ -56,3 +59,7 @@ $primary->command_ok(
        [   'pg_receivewal', '-D',     $stream_dir,     '--verbose',
                '--endpos',      $nextlsn, '--synchronous', '--no-loop' ],
        'streaming some WAL with --synchronous');
+
+# Group access should not be enabled on WAL files
+ok(check_mode_recursive($stream_dir, 0700, 0600),
+   "check stream dir permissions");
diff --git a/src/bin/pg_basebackup/walmethods.c 
b/src/bin/pg_basebackup/walmethods.c
index b4558a0184..aa3bb35d92 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -22,6 +22,7 @@
 #endif
 
 #include "pgtar.h"
+#include "common/file_perm.h"
 #include "common/file_utils.h"
 
 #include "receivelog.h"
@@ -89,7 +90,7 @@ dir_open_for_write(const char *pathname, const char 
*temp_suffix, size_t pad_to_
         * does not do any system calls to fsync() to make changes permanent on
         * disk.
         */
-       fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
+       fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, 
PG_FILE_MODE_DEFAULT);
        if (fd < 0)
                return NULL;
 
@@ -534,7 +535,8 @@ tar_open_for_write(const char *pathname, const char 
*temp_suffix, size_t pad_to_
                 * We open the tar file only when we first try to write to it.
                 */
                tar_data->fd = open(tar_data->tarfilename,
-                                                       O_WRONLY | O_CREAT | 
PG_BINARY, S_IRUSR | S_IWUSR);
+                                                       O_WRONLY | O_CREAT | 
PG_BINARY,
+                                                       PG_FILE_MODE_DEFAULT);
                if (tar_data->fd < 0)
                        return NULL;
 
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 9bc830b085..e83a7179ea 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -25,6 +25,7 @@
 
 #include "catalog/pg_control.h"
 #include "common/controldata_utils.h"
+#include "common/file_perm.h"
 #include "getopt_long.h"
 #include "utils/pidfile.h"
 
@@ -2170,7 +2171,8 @@ main(int argc, char **argv)
         */
        argv0 = argv[0];
 
-       umask(S_IRWXG | S_IRWXO);
+       /* Set dir/file mode mask */
+       umask(PG_MODE_MASK_DEFAULT);
 
        /* support --help and --version even if invoked as root */
        if (argc > 1)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl 
b/src/bin/pg_ctl/t/001_start_stop.pl
index 5da4746cb4..3d82abe696 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -4,7 +4,7 @@ use warnings;
 use Config;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 19;
+use Test::More tests => 20;
 
 my $tempdir       = TestLib::tempdir;
 my $tempdir_short = TestLib::tempdir_short;
@@ -57,10 +57,15 @@ command_ok([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ], 
'pg_ctl stop');
 command_fails([ 'pg_ctl', 'stop', '-D', "$tempdir/data" ],
        'second pg_ctl stop fails');
 
+# Log file for first perm test
+my $logFileName = "$tempdir/data/perm-test-600.log";
+
 command_ok(
-       [ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
+       [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
        'pg_ctl restart with server not running');
-command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
-       'pg_ctl restart with server running');
+
+# Log file should exist and have no group permissions
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0700, 0600));
 
 system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c 
b/src/bin/pg_resetwal/pg_resetwal.c
index eba7c5fdee..49670a0d29 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -52,6 +52,7 @@
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
 #include "common/fe_memutils.h"
+#include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "storage/large_object.h"
 #include "pg_getopt.h"
@@ -362,6 +363,9 @@ main(int argc, char *argv[])
                exit(1);
        }
 
+       /* Set dir/file mode mask */
+       umask(PG_MODE_MASK_DEFAULT);
+
        /* Check that data directory matches our server version */
        CheckDataVersion();
 
@@ -967,7 +971,7 @@ RewriteControlFile(void)
 
        fd = open(XLOG_CONTROL_FILE,
                          O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
-                         S_IRUSR | S_IWUSR);
+                         PG_FILE_MODE_DEFAULT);
        if (fd < 0)
        {
                fprintf(stderr, _("%s: could not create pg_control file: %s\n"),
@@ -1249,7 +1253,7 @@ WriteEmptyXLOG(void)
        unlink(path);
 
        fd = open(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
-                         S_IRUSR | S_IWUSR);
+                         PG_FILE_MODE_DEFAULT);
        if (fd < 0)
        {
                fprintf(stderr, _("%s: could not open file \"%s\": %s\n"),
diff --git a/src/bin/pg_resetwal/t/001_basic.pl 
b/src/bin/pg_resetwal/t/001_basic.pl
index 1b157cb555..7a68a00638 100644
--- a/src/bin/pg_resetwal/t/001_basic.pl
+++ b/src/bin/pg_resetwal/t/001_basic.pl
@@ -3,7 +3,7 @@ use warnings;
 
 use PostgresNode;
 use TestLib;
-use Test::More tests => 11;
+use Test::More tests => 12;
 
 program_help_ok('pg_resetwal');
 program_version_ok('pg_resetwal');
@@ -15,3 +15,6 @@ $node->init;
 command_like([ 'pg_resetwal', '-n', $node->data_dir ],
                         qr/checkpoint/,
                         'pg_resetwal -n produces output');
+
+ok(check_mode_recursive(
+   $node->data_dir, 0700, 0600), 'check PGDATA permissions');
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 00b5b42dd7..7b632c7dcd 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -237,6 +237,10 @@ sub run_pg_rewind
                "$tmp_folder/master-postgresql.conf.tmp",
                "$master_pgdata/postgresql.conf");
 
+       chmod(0600, "$master_pgdata/postgresql.conf")
+               or BAIL_OUT(
+                       "unable to set permissions for 
$master_pgdata/postgresql.conf");
+
        # Plug-in rewound node to the now-promoted standby node
        my $port_standby = $node_standby->port;
        $node_master->append_conf(
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f491ed7f5c..5a7f769dd6 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -18,6 +18,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 
+#include "common/file_perm.h"
 #include "file_ops.h"
 #include "filemap.h"
 #include "logging.h"
@@ -57,7 +58,7 @@ open_target_file(const char *path, bool trunc)
        mode = O_WRONLY | O_CREAT | PG_BINARY;
        if (trunc)
                mode |= O_TRUNC;
-       dstfd = open(dstpath, mode, 0600);
+       dstfd = open(dstpath, mode, PG_FILE_MODE_DEFAULT);
        if (dstfd < 0)
                pg_fatal("could not open target file \"%s\": %s\n",
                                 dstpath, strerror(errno));
@@ -198,7 +199,7 @@ truncate_target_file(const char *path, off_t newsize)
 
        snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
 
-       fd = open(dstpath, O_WRONLY, 0);
+       fd = open(dstpath, O_WRONLY, PG_FILE_MODE_DEFAULT);
        if (fd < 0)
                pg_fatal("could not open file \"%s\" for truncation: %s\n",
                                 dstpath, strerror(errno));
@@ -219,7 +220,7 @@ create_target_dir(const char *path)
                return;
 
        snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
-       if (mkdir(dstpath, S_IRWXU) != 0)
+       if (mkdir(dstpath, PG_DIR_MODE_DEFAULT) != 0)
                pg_fatal("could not create directory \"%s\": %s\n",
                                 dstpath, strerror(errno));
 }
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 72ab2f8d5e..3d179e2c7d 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -24,6 +24,7 @@
 #include "access/xlog_internal.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
+#include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "getopt_long.h"
 #include "storage/bufpage.h"
@@ -185,6 +186,9 @@ main(int argc, char **argv)
                exit(1);
        }
 
+       /* Set dir/file mode mask */
+       umask(PG_MODE_MASK_DEFAULT);
+
        /*
         * Don't allow pg_rewind to be run as root, to avoid overwriting the
         * ownership of files in the data directory. We need only check for root
diff --git a/src/bin/pg_rewind/t/001_basic.pl b/src/bin/pg_rewind/t/001_basic.pl
index 736f34eae3..ae7b116532 100644
--- a/src/bin/pg_rewind/t/001_basic.pl
+++ b/src/bin/pg_rewind/t/001_basic.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 8;
+use Test::More tests => 10;
 
 use RewindTest;
 
@@ -86,6 +86,7 @@ in master, before promotion
 ),
                'tail-copy');
 
+       ok(check_mode_recursive($node_master->data_dir(), 0700, 0600));
        RewindTest::clean_rewind_test();
 }
 
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index f38bfacf02..595d43ee31 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
 #include "postgres_fe.h"
 
 #include "access/visibilitymap.h"
+#include "common/file_perm.h"
 #include "pg_upgrade.h"
 #include "storage/bufpage.h"
 #include "storage/checksum.h"
@@ -44,7 +45,7 @@ copyFile(const char *src, const char *dst,
                                 schemaName, relName, src, strerror(errno));
 
        if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
-                                               S_IRUSR | S_IWUSR)) < 0)
+                                               PG_FILE_MODE_DEFAULT)) < 0)
                pg_fatal("error while copying relation \"%s.%s\": could not 
create file \"%s\": %s\n",
                                 schemaName, relName, dst, strerror(errno));
 
@@ -151,7 +152,7 @@ rewriteVisibilityMap(const char *fromfile, const char 
*tofile,
                                 schemaName, relName, fromfile, 
strerror(errno));
 
        if ((dst_fd = open(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
-                                          S_IRUSR | S_IWUSR)) < 0)
+                                          PG_FILE_MODE_DEFAULT)) < 0)
                pg_fatal("error while copying relation \"%s.%s\": could not 
create file \"%s\": %s\n",
                                 schemaName, relName, tofile, strerror(errno));
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index d12412799f..59df6fd88f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -38,6 +38,7 @@
 
 #include "pg_upgrade.h"
 #include "catalog/pg_class.h"
+#include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "fe_utils/string_utils.h"
 
@@ -79,7 +80,7 @@ main(int argc, char **argv)
        set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
 
        /* Ensure that all files created by pg_upgrade are non-world-readable */
-       umask(S_IRWXG | S_IRWXO);
+       umask(PG_MODE_MASK_DEFAULT);
 
        parseCommandLine(argc, argv);
 
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 39983abea1..4e8db4aaf4 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -230,6 +230,17 @@ standard_initdb 'initdb'
 
 pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" 
-B "$bindir" -p "$PGPORT" -P "$PGPORT"
 
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
+       echo "files in PGDATA with permission != 600";
+       exit 1;
+fi
+
+if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
+       echo "directories in PGDATA with permission != 700";
+       exit 1;
+fi
+
 pg_ctl start -l "$logdir/postmaster2.log" -o "$POSTMASTER_OPTS" -w
 
 case $testhost in
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
new file mode 100644
index 0000000000..3c6da0e000
--- /dev/null
+++ b/src/include/common/file_perm.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission constants
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FILE_PERM_H
+#define FILE_PERM_H
+
+/*
+ * Default mode mask for data directory permissions that does not allow
+ * group execute/read.
+ */
+#define PG_MODE_MASK_DEFAULT           (S_IRWXG | S_IRWXO)
+
+/*
+ * Default mode for created files.
+ */
+#define PG_FILE_MODE_DEFAULT           (S_IRUSR | S_IWUSR)
+
+/*
+ * Default mode for directories.
+ */
+#define PG_DIR_MODE_DEFAULT                    S_IRWXU
+
+#endif                                                 /* FILE_PERM_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index e49b42ce86..340dbd4d3b 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -112,6 +112,9 @@ extern int  CloseTransientFile(int fd);
 extern int     BasicOpenFile(const char *fileName, int fileFlags);
 extern int     BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t 
fileMode);
 
+ /* Make a directory with default permissions */
+extern int MakeDirectoryDefaultPerm(const char *directoryName);
+
 /* Miscellaneous support routines */
 extern void InitFileAccess(void);
 extern void set_max_safe_fds(void);
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 80188315f1..76e571b98c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -484,6 +484,9 @@ sub append_conf
        my $conffile = $self->data_dir . '/' . $filename;
 
        TestLib::append_to_file($conffile, $str . "\n");
+
+    chmod(0600, $conffile)
+        or die("unable to set permissions for $conffile");
 }
 
 =pod
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index b6862688d4..93610e4bc4 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -13,8 +13,11 @@ use warnings;
 use Config;
 use Cwd;
 use Exporter 'import';
+use Fcntl qw(:mode);
 use File::Basename;
+use File::Find;
 use File::Spec;
+use File::stat qw(stat);
 use File::Temp ();
 use IPC::Run;
 use SimpleTee;
@@ -27,6 +30,7 @@ our @EXPORT = qw(
   slurp_dir
   slurp_file
   append_to_file
+  check_mode_recursive
   check_pg_config
   system_or_bail
   system_log
@@ -240,6 +244,75 @@ sub append_to_file
        close $fh;
 }
 
+# Check that all file/dir modes in a directory match the expected values,
+# ignoring the mode of any specified files.
+sub check_mode_recursive
+{
+       my ($dir, $expected_dir_mode, $expected_file_mode, $ignore_list) = @_;
+
+       # Result defaults to true
+       my $result = 1;
+
+       find
+       (
+               {follow_fast => 1,
+               wanted =>
+                       sub
+                       {
+                               my $file_stat = stat($File::Find::name);
+
+                               # Is file in the ignore list?
+                               foreach my $ignore ($ignore_list ? 
@{$ignore_list} : [])
+                               {
+                                       if ("$dir/$ignore" eq $File::Find::name)
+                                       {
+                                               return;
+                                       }
+                               }
+
+                               defined($file_stat)
+                                       or die("unable to stat 
$File::Find::name");
+
+                               my $file_mode = S_IMODE($file_stat->mode);
+
+                               # Is this a file?
+                               if (S_ISREG($file_stat->mode))
+                               {
+                                       if ($file_mode != $expected_file_mode)
+                                       {
+                                               print(*STDERR,
+                                                       
sprintf("$File::Find::name mode must be %04o\n",
+                                                       $expected_file_mode));
+
+                                               $result = 0;
+                                               return;
+                                       }
+                               }
+                               # Else a directory?
+                               elsif (S_ISDIR($file_stat->mode))
+                               {
+                                       if ($file_mode != $expected_dir_mode)
+                                       {
+                                               print(*STDERR,
+                                                       
sprintf("$File::Find::name mode must be %04o\n",
+                                                       $expected_dir_mode));
+
+                                               $result = 0;
+                                               return;
+                                       }
+                               }
+                               # Else something we can't handle
+                               else
+                               {
+                                       die "unknown file type for 
$File::Find::name";
+                               }
+                       }},
+               $dir
+       );
+
+       return $result;
+}
+
 # Check presence of a given regexp within pg_config.h for the installation
 # where tests are running, returning a match status result depending on
 # that.
-- 
2.14.3 (Apple Git-98)

From e5b9b1b14536f39a288656b6fbdbeec346fbea7c Mon Sep 17 00:00:00 2001
From: David Steele <da...@pgmasters.net>
Date: Fri, 30 Mar 2018 13:23:43 -0400
Subject: [PATCH 1/1] Allow group access on PGDATA.

This includes backend changes, utility changes (initdb, pg_ctl, pg_upgrade, 
etc.) and tests for each utility.
---
 doc/src/sgml/ref/initdb.sgml                 | 19 +++++++
 doc/src/sgml/runtime.sgml                    | 14 +++++-
 src/backend/commands/tablespace.c            |  2 +-
 src/backend/postmaster/postmaster.c          | 41 +++++++++++----
 src/backend/replication/basebackup.c         |  4 +-
 src/backend/utils/init/globals.c             |  7 +++
 src/backend/utils/init/miscinit.c            | 22 ++++----
 src/backend/utils/misc/guc.c                 | 25 ++++++++++
 src/bin/initdb/initdb.c                      | 35 ++++++++++---
 src/bin/initdb/t/001_initdb.pl               | 13 ++++-
 src/bin/pg_basebackup/pg_basebackup.c        | 22 +++++---
 src/bin/pg_basebackup/pg_receivewal.c        |  7 +++
 src/bin/pg_basebackup/pg_recvlogical.c       |  7 +++
 src/bin/pg_basebackup/streamutil.c           | 75 ++++++++++++++++++++++++++++
 src/bin/pg_basebackup/streamutil.h           |  2 +
 src/bin/pg_basebackup/t/010_pg_basebackup.pl | 26 +++++++---
 src/bin/pg_ctl/pg_ctl.c                      |  5 +-
 src/bin/pg_ctl/t/001_start_stop.pl           | 24 ++++++++-
 src/bin/pg_resetwal/pg_resetwal.c            |  4 +-
 src/bin/pg_rewind/RewindTest.pm              |  9 ++--
 src/bin/pg_rewind/pg_rewind.c                |  4 +-
 src/bin/pg_rewind/t/002_databases.pl         |  6 ++-
 src/bin/pg_upgrade/pg_upgrade.c              |  5 +-
 src/bin/pg_upgrade/test.sh                   | 14 +++---
 src/common/Makefile                          |  2 +-
 src/common/file_perm.c                       | 48 ++++++++++++++++++
 src/include/common/file_perm.h               | 35 +++++++++++--
 src/include/miscadmin.h                      |  1 +
 src/test/perl/PostgresNode.pm                | 27 +++++++++-
 src/test/perl/TestLib.pm                     | 25 ++++++++++
 src/tools/msvc/Mkvcbuild.pm                  |  2 +-
 31 files changed, 454 insertions(+), 78 deletions(-)
 create mode 100644 src/common/file_perm.c

diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml
index 949b5a220f..1d41e91794 100644
--- a/doc/src/sgml/ref/initdb.sgml
+++ b/doc/src/sgml/ref/initdb.sgml
@@ -76,6 +76,14 @@ PostgreSQL documentation
    to do so.)
   </para>
 
+  <para>
+    For security reasons the new cluster created by <command>initdb</command>
+    will only be accessible by the cluster owner by default.  The
+    <option>--allow-group-access</option> option allows any user in the same
+    group as the cluster owner to read files in the cluster.  This is useful
+    for performing backups as a non-privileged user.
+  </para>
+
   <para>
    <command>initdb</command> initializes the database cluster's default
    locale and character set encoding. The character set encoding,
@@ -301,6 +309,17 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-g</option></term>
+      <term><option>--allow-group-access</option></term>
+      <listitem>
+       <para>
+        Allows users in the same group as the cluster owner to read all cluster
+        files created by <command>initdb</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-W</option></term>
       <term><option>--pwprompt</option></term>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 587b430527..40ce3d9813 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -137,7 +137,10 @@ postgres$ <userinput>initdb -D 
/usr/local/pgsql/data</userinput>
    database, it is essential that it be secured from unauthorized
    access. <command>initdb</command> therefore revokes access
    permissions from everyone but the
-   <productname>PostgreSQL</productname> user.
+   <productname>PostgreSQL</productname> user, and optionally, group.
+   Group access, when enabled, is read-only.  This allows an unprivileged
+   user in the <productname>PostgreSQL</productname> group to take a backup of
+   the cluster data or perform other operations that only require read access.
   </para>
 
   <para>
@@ -2194,6 +2197,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
    member of the group that has access to those certificate and key files.
   </para>
 
+  <para>
+    If the data directory allows group read access then certificate files may
+    need to be located outside of the data directory in order to conform to the
+    security requirements outlined above.  Generally, group access is enabled
+    to allow an unprivileged user to backup the database, and in that case the
+    backup software will not be able to read the certificate files and will
+    likely error.
+  </para>
+
   <para>
    If the private key is protected with a passphrase, the
    server will prompt for the passphrase and will not start until it has
diff --git a/src/backend/commands/tablespace.c 
b/src/backend/commands/tablespace.c
index f7e1818350..704ff6c3df 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -576,7 +576,7 @@ create_tablespace_directories(const char *location, const 
Oid tablespaceoid)
         * Attempt to coerce target directory to safe permissions.  If this 
fails,
         * it doesn't exist or has the wrong owner.
         */
-       if (chmod(location, PG_DIR_MODE_DEFAULT) != 0)
+       if (chmod(location, DataDirMode) != 0)
        {
                if (errno == ENOENT)
                        ereport(ERROR,
diff --git a/src/backend/postmaster/postmaster.c 
b/src/backend/postmaster/postmaster.c
index 868fba8cac..2f543ef461 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -588,7 +588,9 @@ PostmasterMain(int argc, char *argv[])
        IsPostmasterEnvironment = true;
 
        /*
-        * for security, no dir or file created can be group or other accessible
+        * By default, no directory or file created can be group or other
+        * accessible.  This may be modified later depending on the permissions 
of
+        * the data directory.
         */
        umask(PG_MODE_MASK_DEFAULT);
 
@@ -1522,25 +1524,44 @@ checkDataDir(void)
 #endif
 
        /*
-        * Check if the directory has group or world access.  If so, reject.
+        * Check if the directory has correct permissions.  If not, reject.
         *
-        * It would be possible to allow weaker constraints (for example, allow
-        * group access) but we cannot make a general assumption that that is
-        * okay; for example there are platforms where nearly all users
-        * customarily belong to the same group.  Perhaps this test should be
-        * configurable.
+        * Only two possible modes are allowed, 0700 and 0750.  The latter mode
+        * indicates that group read/execute should be allowed on all newly 
created
+        * files and directories.
         *
         * XXX temporarily suppress check when on Windows, because there may not
         * be proper support for Unix-y file permissions.  Need to think of a
         * reasonable check to apply on Windows.
         */
 #if !defined(WIN32) && !defined(__CYGWIN__)
-       if (stat_buf.st_mode & (S_IRWXG | S_IRWXO))
+       if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP)
                ereport(FATAL,
                                
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                errmsg("data directory \"%s\" has group or 
world access",
+                                errmsg("data directory \"%s\" has invalid 
permissions",
                                                DataDir),
-                                errdetail("Permissions should be u=rwx 
(0700).")));
+                                errdetail("Permissions should be u=rwx (0700) 
or u=rwx,g=rx (0750).")));
+#endif
+
+       /*
+        * Reset the file mode creation mask based on the mode of the data
+        * directory. The mask was set earlier in startup to disallow group
+        * permissions on newly created files and directories.  However, if 
group
+        * read/execute are present on the data directory then modify the mask 
to
+        * allow group read/execute on newly created files and directories and 
set
+        * the data_directory_mode GUC.
+        *
+        * Suppress when on Windows, because there may not be proper support
+        * for Unix-y file permissions.
+        */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+       if ((stat_buf.st_mode & PG_DIR_MODE_DEFAULT) == PG_DIR_MODE_DEFAULT)
+       {
+               umask(PG_MODE_MASK_ALLOW_GROUP);
+
+               SetConfigOption("data_directory_mode", "0750", PGC_INTERNAL,
+                                               PGC_S_OVERRIDE);
+       }
 #endif
 
        /* Look for PG_VERSION before looking for pg_control */
diff --git a/src/backend/replication/basebackup.c 
b/src/backend/replication/basebackup.c
index 97e2bdfc73..778d2dbed3 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -879,7 +879,7 @@ sendFileWithContent(const char *filename, const char 
*content)
        statbuf.st_gid = getegid();
 #endif
        statbuf.st_mtime = time(NULL);
-       statbuf.st_mode = PG_FILE_MODE_DEFAULT;
+       statbuf.st_mode = DataDirMode & PG_FILE_MODE_DEFAULT;
        statbuf.st_size = len;
 
        _tarWriteHeader(filename, NULL, &statbuf, false);
@@ -1398,7 +1398,7 @@ _tarWriteDir(const char *pathbuf, int basepathlen, struct 
stat *statbuf,
 #else
        if (pgwin32_is_junction(pathbuf))
 #endif
-               statbuf->st_mode = S_IFDIR | PG_DIR_MODE_DEFAULT;
+               statbuf->st_mode = S_IFDIR | DataDirMode;
 
        return _tarWriteHeader(pathbuf + basepathlen + 1, NULL, statbuf, 
sizeonly);
 }
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 446040d816..10a7ac6c42 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -18,6 +18,7 @@
  */
 #include "postgres.h"
 
+#include "common/file_perm.h"
 #include "libpq/libpq-be.h"
 #include "libpq/pqcomm.h"
 #include "miscadmin.h"
@@ -59,6 +60,12 @@ struct Latch *MyLatch;
  */
 char      *DataDir = NULL;
 
+/*
+ * Mode of the data directory.  The default is 0700 but may it be changed in
+ * checkDataDir() to 0750 if the data directory actually has that mode.
+ */
+int DataDirMode = PG_DIR_MODE_NOGROUP;
+
 char           OutputFileName[MAXPGPATH];      /* debugging output file */
 
 char           my_exec_path[MAXPGPATH];        /* full path to my executable */
diff --git a/src/backend/utils/init/miscinit.c 
b/src/backend/utils/init/miscinit.c
index 82da4b9f27..192e0afa81 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -125,7 +125,6 @@ ChangeToDataDir(void)
                                                DataDir)));
 }
 
-
 /* ----------------------------------------------------------------
  *     User ID state
  *
@@ -829,7 +828,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
                /*
                 * Try to create the lock file --- O_EXCL makes this atomic.
                 *
-                * Think not to make the file protection weaker than 0600.  See
+                * Think not to make the file protection weaker than 0600/0640. 
 See
                 * comments below.
                 */
                fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 
PG_FILE_MODE_DEFAULT);
@@ -899,17 +898,14 @@ CreateLockFile(const char *filename, bool amPostmaster,
                 * implies that the existing process has a different userid 
than we
                 * do, which means it cannot be a competing postmaster.  A 
postmaster
                 * cannot successfully attach to a data directory owned by a 
userid
-                * other than its own.  (This is now checked directly in
-                * checkDataDir(), but has been true for a long time because of 
the
-                * restriction that the data directory isn't group- or
-                * world-accessible.)  Also, since we create the lockfiles mode 
600,
-                * we'd have failed above if the lockfile belonged to another 
userid
-                * --- which means that whatever process kill() is reporting 
about
-                * isn't the one that made the lockfile.  (NOTE: this last
-                * consideration is the only one that keeps us from blowing 
away a
-                * Unix socket file belonging to an instance of Postgres being 
run by
-                * someone else, at least on machines where /tmp hasn't got a
-                * stickybit.)
+                * other than its own, as enforced in checkDataDir(). Also, 
since we
+                * create the lockfiles mode 0600/0640, we'd have failed above 
if the
+                * lockfile belonged to another userid --- which means that 
whatever
+                * process kill() is reporting about isn't the one that made the
+                * lockfile.  (NOTE: this last consideration is the only one 
that keeps
+                * us from blowing away a Unix socket file belonging to an 
instance of
+                * Postgres being run by someone else, at least on machines 
where /tmp
+                * hasn't got a stickybit.)
                 */
                if (other_pid != my_pid && other_pid != my_p_pid &&
                        other_pid != my_gp_pid)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 4ffc8451ca..33b9ad53e3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -192,6 +192,7 @@ static void assign_application_name(const char *newval, 
void *extra);
 static bool check_cluster_name(char **newval, void **extra, GucSource source);
 static const char *show_unix_socket_permissions(void);
 static const char *show_log_file_mode(void);
+static const char *show_data_directory_mode(void);
 
 /* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -2042,6 +2043,21 @@ static struct config_int ConfigureNamesInt[] =
                NULL, NULL, show_log_file_mode
        },
 
+
+       {
+               {"data_directory_mode", PGC_INTERNAL, PRESET_OPTIONS,
+                       gettext_noop("Mode of the data directory."),
+                       gettext_noop("The parameter value is a numeric mode 
specification "
+                                                "in the form accepted by the 
chmod and umask system "
+                                                "calls. (To use the customary 
octal format the number "
+                                                "must start with a 0 
(zero).)"),
+                       GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+               },
+               &DataDirMode,
+               0700, 0000, 0777,
+               NULL, NULL, show_data_directory_mode
+       },
+
        {
                {"work_mem", PGC_USERSET, RESOURCES_MEM,
                        gettext_noop("Sets the maximum memory to be used for 
query workspaces."),
@@ -10721,4 +10737,13 @@ show_log_file_mode(void)
        return buf;
 }
 
+static const char *
+show_data_directory_mode(void)
+{
+       static char buf[12];
+
+       snprintf(buf, sizeof(buf), "%04o", DataDirMode);
+       return buf;
+}
+
 #include "guc-file.c"
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 0b69eded81..96f162fb0b 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -145,6 +145,7 @@ static bool data_checksums = false;
 static char *xlog_dir = NULL;
 static char *str_wal_segment_size_mb = NULL;
 static int     wal_segment_size_mb;
+static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT;
 
 
 /* internal vars */
@@ -1168,10 +1169,23 @@ setup_config(void)
                                                                  
"password_encryption = scram-sha-256");
        }
 
+       /*
+        * If group access has been enabled for the cluster then it makes sense
+        * to ensure that the log files also allow group access.  Otherwise a
+        * backup from a user in the group would fail if the log files were not
+        * relocated.
+        */
+       if (file_mode_mask == PG_MODE_MASK_ALLOW_GROUP)
+       {
+               conflines = replace_token(conflines,
+                                                                 
"#log_file_mode = 0600",
+                                                                 
"log_file_mode = 0640");
+       }
+
        snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
 
        writefile(path, conflines);
-       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -1191,7 +1205,7 @@ setup_config(void)
        sprintf(path, "%s/postgresql.auto.conf", pg_data);
 
        writefile(path, autoconflines);
-       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -1278,7 +1292,7 @@ setup_config(void)
        snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data);
 
        writefile(path, conflines);
-       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -1294,7 +1308,7 @@ setup_config(void)
        snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data);
 
        writefile(path, conflines);
-       if (chmod(path, PG_FILE_MODE_DEFAULT) != 0)
+       if (chmod(path, PG_FILE_MODE_DEFAULT & ~file_mode_mask) != 0)
        {
                fprintf(stderr, _("%s: could not change permissions of \"%s\": 
%s\n"),
                                progname, path, strerror(errno));
@@ -2312,6 +2326,7 @@ usage(const char *progname)
        printf(_("      --auth-local=METHOD   default authentication method for 
local-socket connections\n"));
        printf(_(" [-D, --pgdata=]DATADIR     location for this database 
cluster\n"));
        printf(_("  -E, --encoding=ENCODING   set default encoding for new 
databases\n"));
+       printf(_("  -g, --allow-group-access  allow group read/execute on data 
directory\n"));
        printf(_("      --locale=LOCALE       set default locale for new 
databases\n"));
        printf(_("      --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
                         "      --lc-monetary=, --lc-numeric=, 
--lc-time=LOCALE\n"
@@ -2711,7 +2726,7 @@ create_data_directory(void)
                                   pg_data);
                        fflush(stdout);
 
-                       if (chmod(pg_data, PG_DIR_MODE_DEFAULT) != 0)
+                       if (chmod(pg_data, PG_DIR_MODE_DEFAULT & 
~file_mode_mask) != 0)
                        {
                                fprintf(stderr, _("%s: could not change 
permissions of directory \"%s\": %s\n"),
                                                progname, pg_data, 
strerror(errno));
@@ -2797,7 +2812,7 @@ create_xlog_or_symlink(void)
                                           xlog_dir);
                                fflush(stdout);
 
-                               if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT) != 0)
+                               if (chmod(xlog_dir, PG_DIR_MODE_DEFAULT & 
~file_mode_mask) != 0)
                                {
                                        fprintf(stderr, _("%s: could not change 
permissions of directory \"%s\": %s\n"),
                                                        progname, xlog_dir, 
strerror(errno));
@@ -2884,7 +2899,7 @@ initialize_data_directory(void)
        setup_signals();
 
        /* Set dir/file mode mask */
-       umask(PG_MODE_MASK_DEFAULT);
+       umask(file_mode_mask);
 
        create_data_directory();
 
@@ -3018,6 +3033,7 @@ main(int argc, char *argv[])
                {"waldir", required_argument, NULL, 'X'},
                {"wal-segsize", required_argument, NULL, 12},
                {"data-checksums", no_argument, NULL, 'k'},
+               {"allow-group-access", no_argument, NULL, 'g'},
                {NULL, 0, NULL, 0}
        };
 
@@ -3059,7 +3075,7 @@ main(int argc, char *argv[])
 
        /* process command-line options */
 
-       while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", 
long_options, &option_index)) != -1)
+       while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", 
long_options, &option_index)) != -1)
        {
                switch (c)
                {
@@ -3153,6 +3169,9 @@ main(int argc, char *argv[])
                        case 12:
                                str_wal_segment_size_mb = pg_strdup(optarg);
                                break;
+                       case 'g':
+                               file_mode_mask = PG_MODE_MASK_ALLOW_GROUP;
+                               break;
                        default:
                                /* getopt_long already emitted a complaint */
                                fprintf(stderr, _("Try \"%s --help\" for more 
information.\n"),
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
index 3331560445..f08fc6ea8f 100644
--- a/src/bin/initdb/t/001_initdb.pl
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -4,9 +4,11 @@
 
 use strict;
 use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
 use PostgresNode;
 use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
 
 my $tempdir = TestLib::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
@@ -50,3 +52,12 @@ mkdir $datadir;
 }
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Init a new db with group access
+my $datadir_group = "$tempdir/data_group";
+
+command_ok(
+       [ 'initdb', '-g', $datadir_group ],
+       'successful creation with group access');
+
+ok(check_mode_recursive($datadir_group, 0750, 0640));
diff --git a/src/bin/pg_basebackup/pg_basebackup.c 
b/src/bin/pg_basebackup/pg_basebackup.c
index 3d6e47008a..db3ed9e0fe 100644
--- a/src/bin/pg_basebackup/pg_basebackup.c
+++ b/src/bin/pg_basebackup/pg_basebackup.c
@@ -2448,14 +2448,6 @@ main(int argc, char **argv)
        }
 #endif
 
-       /*
-        * Verify that the target directory exists, or create it. For plaintext
-        * backups, always require the directory. For tar backups, require it
-        * unless we are writing to stdout.
-        */
-       if (format == 'p' || strcmp(basedir, "-") != 0)
-               verify_dir_is_empty_or_create(basedir, &made_new_pgdata, 
&found_existing_pgdata);
-
        /* connection in replication mode to server */
        conn = GetConnection();
        if (!conn)
@@ -2464,6 +2456,20 @@ main(int argc, char **argv)
                exit(1);
        }
 
+       /*
+        * Set umask so that directories/files are created with the same 
permissions
+        * as directories/files in the source data directory.
+        */
+       umask(DataDirMask);
+
+       /*
+        * Verify that the target directory exists, or create it. For plaintext
+        * backups, always require the directory. For tar backups, require it
+        * unless we are writing to stdout.
+        */
+       if (format == 'p' || strcmp(basedir, "-") != 0)
+               verify_dir_is_empty_or_create(basedir, &made_new_pgdata, 
&found_existing_pgdata);
+
        /* determine remote server's xlog segment size */
        if (!RetrieveWalSegSize(conn))
                disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_receivewal.c 
b/src/bin/pg_basebackup/pg_receivewal.c
index b82e073b86..643fb16db1 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -19,6 +19,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "common/file_perm.h"
 #include "libpq-fe.h"
 #include "access/xlog_internal.h"
 #include "getopt_long.h"
@@ -703,6 +704,12 @@ main(int argc, char **argv)
        if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
                disconnect_and_exit(1);
 
+       /*
+        * Set umask so that directories/files are created with the same 
permissions
+        * as directories/files in the source data directory.
+        */
+       umask(DataDirMask);
+
        /* determine remote server's xlog segment size */
        if (!RetrieveWalSegSize(conn))
                disconnect_and_exit(1);
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c 
b/src/bin/pg_basebackup/pg_recvlogical.c
index 53e4661d68..b0f001cd1c 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -23,6 +23,7 @@
 #include "streamutil.h"
 
 #include "access/xlog_internal.h"
+#include "common/file_perm.h"
 #include "common/fe_memutils.h"
 #include "getopt_long.h"
 #include "libpq-fe.h"
@@ -959,6 +960,12 @@ main(int argc, char **argv)
                disconnect_and_exit(1);
        }
 
+       /*
+        * Set umask so that directories/files are created with the same 
permissions
+        * as directories/files in the source data directory.
+        */
+       umask(DataDirMask);
+
        /* Drop a replication slot. */
        if (do_drop_slot)
        {
diff --git a/src/bin/pg_basebackup/streamutil.c 
b/src/bin/pg_basebackup/streamutil.c
index 1438f368ed..a072d3c658 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -23,6 +23,7 @@
 
 #include "access/xlog_internal.h"
 #include "common/fe_memutils.h"
+#include "common/file_perm.h"
 #include "datatype/timestamp.h"
 #include "fe_utils/connect.h"
 #include "port/pg_bswap.h"
@@ -32,9 +33,20 @@
 
 uint32         WalSegSz;
 
+/* Store the mode and umask of the source data directory */
+int DataDirMode;
+int DataDirMask;
+
+static bool RetrieveDataDirMode(PGconn *conn);
+
 /* SHOW command for replication connection was introduced in version 10 */
 #define MINIMUM_VERSION_FOR_SHOW_CMD 100000
 
+/*
+ * Group access is supported from version 11.
+ */
+#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
+
 const char *progname;
 char      *connection_string = NULL;
 char      *dbhost = NULL;
@@ -254,6 +266,18 @@ GetConnection(void)
                exit(1);
        }
 
+       /*
+        * Retrieve the source data directory mode and use to construct a umask
+        * for creating directories and files
+        */
+       if (!RetrieveDataDirMode(tmpconn))
+       {
+               PQfinish(tmpconn);
+               exit(1);
+       }
+
+       DataDirMask = ~DataDirMode & PG_MODE_MASK_DEFAULT;
+
        return tmpconn;
 }
 
@@ -327,6 +351,57 @@ RetrieveWalSegSize(PGconn *conn)
        return true;
 }
 
+/*
+ * From version 11, check the mode of the data directory to determine
+ * permissions to use for directories created by the utility.
+ */
+static bool
+RetrieveDataDirMode(PGconn *conn)
+{
+       PGresult   *res;
+
+       /* check connection existence */
+       Assert(conn != NULL);
+
+       /* for previous versions set the default group access */
+       if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
+       {
+               DataDirMode = PG_DIR_MODE_NOGROUP;
+               return true;
+       }
+
+       res = PQexec(conn, "SHOW data_directory_mode");
+       if (PQresultStatus(res) != PGRES_TUPLES_OK)
+       {
+               fprintf(stderr, _("%s: could not send replication command 
\"%s\": %s\n"),
+                               progname, "SHOW data_directory_mode", 
PQerrorMessage(conn));
+
+               PQclear(res);
+               return false;
+       }
+       if (PQntuples(res) != 1 || PQnfields(res) < 1)
+       {
+               fprintf(stderr,
+                               _("%s: could not fetch group access flag: got 
%d rows and %d fields, expected %d rows and %d or more fields\n"),
+                               progname, PQntuples(res), PQnfields(res), 1, 1);
+
+               PQclear(res);
+               return false;
+       }
+
+       if (sscanf(PQgetvalue(res, 0, 0), "%o", &DataDirMode) != 1)
+       {
+               fprintf(stderr, _("%s: group access flag could not be parsed: 
%s\n"),
+                               progname, PQgetvalue(res, 0, 0));
+
+               PQclear(res);
+               return false;
+       }
+
+       PQclear(res);
+       return true;
+}
+
 /*
  * Run IDENTIFY_SYSTEM through a given connection and give back to caller
  * some result information if requested:
diff --git a/src/bin/pg_basebackup/streamutil.h 
b/src/bin/pg_basebackup/streamutil.h
index 6854bbc31d..df8fb36a2e 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -25,6 +25,8 @@ extern char *dbport;
 extern char *dbname;
 extern int     dbgetpassword;
 extern uint32 WalSegSz;
+extern int DataDirMode;
+extern int DataDirMask;
 
 /* Connection kept global so we can disconnect easily */
 extern PGconn *conn;
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl 
b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index 8d9f9c21f4..ce87f77e37 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -5,7 +5,7 @@ use Config;
 use File::Basename qw(basename dirname);
 use PostgresNode;
 use TestLib;
-use Test::More tests => 94;
+use Test::More tests => 95;
 
 program_help_ok('pg_basebackup');
 program_version_ok('pg_basebackup');
@@ -43,11 +43,6 @@ $node->command_fails(
 
 ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
 
-$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
-       'failing run with no-clean option');
-
-ok(-d "$tempdir/backup", 'backup directory was created and left behind');
-
 open my $conf, '>>', "$pgdata/postgresql.conf";
 print $conf "max_replication_slots = 10\n";
 print $conf "max_wal_senders = 10\n";
@@ -157,6 +152,13 @@ ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was 
created');
 $node->command_fails(
        [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ],
        '-T with empty old directory fails');
+
+$node->command_fails(
+       [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo", "-n" 
],
+       'failing run with no-clean option');
+
+ok(-d "$tempdir/backup", 'backup directory was created and left behind');
+
 $node->command_fails(
        [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ],
        '-T with empty new directory fails');
@@ -190,11 +192,17 @@ unlink "$pgdata/$superlongname";
 # skip on Windows.
 SKIP:
 {
-       skip "symlinks not supported on Windows", 17 if ($windows_os);
+       skip "symlinks not supported on Windows", 18 if ($windows_os);
 
        # Move pg_replslot out of $pgdata and create a symlink to it.
        $node->stop;
 
+       # Set umask so test directories and files are created with group 
permissions
+       umask (0027);
+
+       # Enable group permissions on PGDATA
+       chmod_recursive("$pgdata", 0750, 0640);
+
        rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
          or BAIL_OUT "could not move $pgdata/pg_replslot";
        symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -264,6 +272,10 @@ SKIP:
                "tablespace symlink was updated");
        closedir $dh;
 
+       # Group access should be enabled on all backup files
+       ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
+          "check backup dir permissions");
+
        # Unlogged relation forks other than init should not be copied
        my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ 
/[^\/]*\/[^\/]*\/[^\/]*$/g;
 
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index e83a7179ea..bb70e625fd 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
         */
        argv0 = argv[0];
 
-       /* Set dir/file mode mask */
+       /* Set restrictive mode mask until PGDATA permissions are checked */
        umask(PG_MODE_MASK_DEFAULT);
 
        /* support --help and --version even if invoked as root */
@@ -2407,6 +2407,9 @@ main(int argc, char **argv)
                snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
                snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
                snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+               /* Set mask based on PGDATA permissions */
+               umask(DataDirectoryMask(pg_data));
        }
 
        switch (ctl_command)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl 
b/src/bin/pg_ctl/t/001_start_stop.pl
index 3d82abe696..91e7f00326 100644
--- a/src/bin/pg_ctl/t/001_start_stop.pl
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -2,9 +2,11 @@ use strict;
 use warnings;
 
 use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
 use PostgresNode;
 use TestLib;
-use Test::More tests => 20;
+use Test::More tests => 24;
 
 my $tempdir       = TestLib::tempdir;
 my $tempdir_short = TestLib::tempdir_short;
@@ -68,4 +70,24 @@ command_ok(
 ok(-f $logFileName);
 ok(check_mode_recursive("$tempdir/data", 0700, 0600));
 
+# Log file for second perm test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+# Change the data dir mode so log file will be created with group read
+# privileges on the next start
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+chmod_recursive("$tempdir/data", 0750, 0640);
+
+command_ok(
+       [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+       'start server to check group permissions');
+
+ok(-f $logFileName);
+ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+
+command_ok(
+       [ 'pg_ctl', 'restart', '-D', "$tempdir/data", '-l', $logFileName ],
+       'pg_ctl restart with server running');
+
 system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
diff --git a/src/bin/pg_resetwal/pg_resetwal.c 
b/src/bin/pg_resetwal/pg_resetwal.c
index 49670a0d29..a311bb4128 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -363,8 +363,8 @@ main(int argc, char *argv[])
                exit(1);
        }
 
-       /* Set dir/file mode mask */
-       umask(PG_MODE_MASK_DEFAULT);
+       /* Set mask based on PGDATA permissions */
+       umask(DataDirectoryMask(DataDir));
 
        /* Check that data directory matches our server version */
        CheckDataVersion();
diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm
index 7b632c7dcd..63d9bd517d 100644
--- a/src/bin/pg_rewind/RewindTest.pm
+++ b/src/bin/pg_rewind/RewindTest.pm
@@ -115,11 +115,13 @@ sub check_query
 
 sub setup_cluster
 {
-       my $extra_name = shift;
+       my $extra_name = shift;         # Used to differentiate clusters
+       my $extra = shift;                      # Extra params for initdb
 
        # Initialize master, data checksums are mandatory
        $node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" 
: ''));
-       $node_master->init(allows_streaming => 1);
+       $node_master->init(
+               allows_streaming => 1, extra => $extra);
        # Set wal_keep_segments to prevent WAL segment recycling after enforced
        # checkpoints in the tests.
        $node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
                "$tmp_folder/master-postgresql.conf.tmp",
                "$master_pgdata/postgresql.conf");
 
-       chmod(0600, "$master_pgdata/postgresql.conf")
+       chmod($node_master->group_access() ? 0640 : 0600,
+                 "$master_pgdata/postgresql.conf")
                or BAIL_OUT(
                        "unable to set permissions for 
$master_pgdata/postgresql.conf");
 
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 3d179e2c7d..11153e5f2f 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -186,8 +186,8 @@ main(int argc, char **argv)
                exit(1);
        }
 
-       /* Set dir/file mode mask */
-       umask(PG_MODE_MASK_DEFAULT);
+       /* Set mask based on PGDATA permissions */
+       umask(DataDirectoryMask(datadir_target));
 
        /*
         * Don't allow pg_rewind to be run as root, to avoid overwriting the
diff --git a/src/bin/pg_rewind/t/002_databases.pl 
b/src/bin/pg_rewind/t/002_databases.pl
index 37cdd712f3..570fa5cee0 100644
--- a/src/bin/pg_rewind/t/002_databases.pl
+++ b/src/bin/pg_rewind/t/002_databases.pl
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
 
 use RewindTest;
 
@@ -9,7 +9,7 @@ sub run_test
 {
        my $test_mode = shift;
 
-       RewindTest::setup_cluster($test_mode);
+       RewindTest::setup_cluster($test_mode, ['-g']);
        RewindTest::start_master();
 
        # Create a database in master.
@@ -42,6 +42,8 @@ template1
 ),
                'database names');
 
+       ok (check_mode_recursive(
+               $node_master->data_dir(), 0750, 0640, ['postmaster.pid']));
        RewindTest::clean_rewind_test();
 }
 
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 59df6fd88f..54f54fb0a4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -79,7 +79,7 @@ main(int argc, char **argv)
 
        set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
 
-       /* Ensure that all files created by pg_upgrade are non-world-readable */
+       /* Set default restrictive mask until new cluster permissions are read 
*/
        umask(PG_MODE_MASK_DEFAULT);
 
        parseCommandLine(argc, argv);
@@ -100,6 +100,9 @@ main(int argc, char **argv)
 
        check_cluster_compatibility(live_check);
 
+       /* Set mask based on new PGDATA permissions */
+       umask(DataDirectoryMask(new_cluster.pgdata));
+
        check_and_dump_old_cluster(live_check);
 
 
diff --git a/src/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index 4e8db4aaf4..24631a91c6 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -20,9 +20,9 @@ unset MAKELEVEL
 # Run a given "initdb" binary and overlay the regression testing
 # authentication configuration.
 standard_initdb() {
-       # To increase coverage of non-standard segment size without
-       # increase test runtime, run these tests with a lower setting.
-       "$1" -N --wal-segsize 1
+       # To increase coverage of non-standard segment size and group access
+       # without increasing test runtime, run these tests with a custom 
setting.
+       "$1" -N --wal-segsize 1 -g
        if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
        then
                cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -231,13 +231,13 @@ standard_initdb 'initdb'
 pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" 
-B "$bindir" -p "$PGPORT" -P "$PGPORT"
 
 # make sure all directories and files have group permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
-       echo "files in PGDATA with permission != 600";
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+       echo "files in PGDATA with permission != 640";
        exit 1;
 fi
 
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
-       echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+       echo "directories in PGDATA with permission != 750";
        exit 1;
 fi
 
diff --git a/src/common/Makefile b/src/common/Makefile
index 80e78d72fe..504375bef4 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -51,7 +51,7 @@ else
 OBJS_COMMON += sha2.o
 endif
 
-OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
+OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_perm.o file_utils.o 
restricted_token.o
 
 OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
 
diff --git a/src/common/file_perm.c b/src/common/file_perm.c
new file mode 100644
index 0000000000..b490220b64
--- /dev/null
+++ b/src/common/file_perm.c
@@ -0,0 +1,48 @@
+/*-------------------------------------------------------------------------
+ *
+ * File and directory permission routines
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/file_perm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#error "This file is not expected to be compiled for backend code"
+#endif
+
+#include <sys/stat.h>
+
+#include "common/file_perm.h"
+
+/*
+ * Determine the mask to use when writing to PGDATA by examining the mode of 
the
+ * PGDATA directory.  If group read/execute are present in the PGDATA directory
+ * mode, the mask will be relaxed to allow group read/execute on all newly
+ * created files and directories.
+ *
+ * Errors are not handled here and should be checked by the frontend
+ * application.
+ */
+mode_t
+DataDirectoryMask(const char *dataDir)
+{
+       struct stat statBuf;
+
+       /*
+        * If an error occurs getting the mode then return the more restrictive
+        * mask. It is the reponsibility of the frontend application to 
generate an
+        * error if the PGDATA directory cannot be accessed.
+        */
+       if (stat(dataDir, &statBuf) != 0)
+               return PG_MODE_MASK_DEFAULT;
+
+       /*
+        * Construct the mask that the caller should pass to umask().
+        */
+       return PG_MODE_MASK_DEFAULT & ~statBuf.st_mode;
+}
diff --git a/src/include/common/file_perm.h b/src/include/common/file_perm.h
index 3c6da0e000..4266f7780a 100644
--- a/src/include/common/file_perm.h
+++ b/src/include/common/file_perm.h
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  *
- * File and directory permission constants
+ * File and directory permission constants and routines
  *
  *
  * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
@@ -13,6 +13,8 @@
 #ifndef FILE_PERM_H
 #define FILE_PERM_H
 
+#include <sys/stat.h>
+
 /*
  * Default mode mask for data directory permissions that does not allow
  * group execute/read.
@@ -20,13 +22,36 @@
 #define PG_MODE_MASK_DEFAULT           (S_IRWXG | S_IRWXO)
 
 /*
- * Default mode for created files.
+ * Optional mode mask for data directory permissions that allows group
+ * read/execute.
+ */
+#define PG_MODE_MASK_ALLOW_GROUP       (S_IWGRP | S_IRWXO)
+
+/*
+ * Default mode for created files, unless something else is specified using
+ * the *Perm() function variants.
+ */
+#define PG_FILE_MODE_DEFAULT           (S_IRUSR | S_IWUSR | S_IRGRP)
+
+/*
+ * Default mode for directories created with MakeDirectoryDefaultPerm().
  */
-#define PG_FILE_MODE_DEFAULT           (S_IRUSR | S_IWUSR)
+#define PG_DIR_MODE_DEFAULT                    (S_IRWXU | S_IRGRP | S_IXGRP)
 
 /*
- * Default mode for directories.
+ * Mode for directories that don't allow group access, even when it is enabled
+ * on PGDATA.
  */
-#define PG_DIR_MODE_DEFAULT                    S_IRWXU
+#define PG_DIR_MODE_NOGROUP                    S_IRWXU
+
+#ifdef FRONTEND
+
+/*
+ * Determine the mask to use when writing to PGDATA
+ */
+mode_t DataDirectoryMask(const char *dataDir);
+
+#endif                                                 /* FRONTEND */
+
 
 #endif                                                 /* FILE_PERM_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index a4574cd533..4d55b9d029 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
 extern PGDLLIMPORT bool ExitOnAnyError;
 
 extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int DataDirMode;
 
 extern PGDLLIMPORT int NBuffers;
 extern PGDLLIMPORT int MaxBackends;
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 76e571b98c..1bd91524d7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -86,9 +86,11 @@ use Carp;
 use Config;
 use Cwd;
 use Exporter 'import';
+use Fcntl qw(:mode);
 use File::Basename;
 use File::Path qw(rmtree);
 use File::Spec;
+use File::stat qw(stat);
 use File::Temp ();
 use IPC::Run;
 use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
 
 =pod
 
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+       my ($self) = @_;
+
+       my $dir_stat = stat($self->data_dir);
+
+       defined($dir_stat)
+               or die('unable to stat ' . $self->data_dir);
+
+       return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
 =item $node->data_dir()
 
 Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
        }
        close $conf;
 
+    chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+        or die("unable to set permissions for $pgdata/postgresql.conf");
+
        $self->set_replication_conf if $params{allows_streaming};
        $self->enable_archiving     if $params{has_archiving};
 }
@@ -485,7 +510,7 @@ sub append_conf
 
        TestLib::append_to_file($conffile, $str . "\n");
 
-    chmod(0600, $conffile)
+    chmod($self->group_access() ? 0640 : 0600, $conffile)
         or die("unable to set permissions for $conffile");
 }
 
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 93610e4bc4..8047404efd 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -31,6 +31,7 @@ our @EXPORT = qw(
   slurp_file
   append_to_file
   check_mode_recursive
+  chmod_recursive
   check_pg_config
   system_or_bail
   system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
        return $result;
 }
 
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+       my ($dir, $dir_mode, $file_mode) = @_;
+
+       find
+       (
+               {follow_fast => 1,
+               wanted =>
+                       sub
+                       {
+                               my $file_stat = stat($File::Find::name);
+
+                               if (defined($file_stat))
+                               {
+                                       chmod(S_ISDIR($file_stat->mode) ? 
$dir_mode : $file_mode,
+                                                 $File::Find::name)
+                                               or die "unable to chmod 
$File::Find::name";
+                               }
+                       }},
+               $dir
+       );
+}
+
 # Check presence of a given regexp within pg_config.h for the installation
 # where tests are running, returning a match status result depending on
 # that.
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f3ec75f874..26e6fc090f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -126,7 +126,7 @@ sub mkvcbuild
        }
 
        our @pgcommonfrontendfiles = (
-               @pgcommonallfiles, qw(fe_memutils.c file_utils.c
+               @pgcommonallfiles, qw(fe_memutils.c file_perm.c file_utils.c
                  restricted_token.c));
 
        our @pgcommonbkndfiles = @pgcommonallfiles;
-- 
2.14.3 (Apple Git-98)

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to