mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-23 15:16:29 +00:00
In Barman mode, if there is an existing, populated data directory, and the "--force" option is provided, the entire directory was being deleted, and later recreated as part of the rsync process, but with the default permissions. Fix this by recreating the data directory with the correct permissions after deleting it.
401 lines
8.4 KiB
C
401 lines
8.4 KiB
C
/*
|
|
*
|
|
* dirmod.c
|
|
* directory handling functions
|
|
*
|
|
* Copyright (c) 2ndQuadrant, 2010-2019
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <signal.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ftw.h>
|
|
|
|
/* NB: postgres_fe must be included BEFORE check_dir */
|
|
#include <libpq-fe.h>
|
|
#include <postgres_fe.h>
|
|
|
|
#include "dirutil.h"
|
|
#include "strutil.h"
|
|
#include "log.h"
|
|
#include "controldata.h"
|
|
|
|
static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
|
|
|
|
/* PID can be negative if backend is standalone */
|
|
typedef long pgpid_t;
|
|
|
|
|
|
/*
|
|
* Check if a directory exists, and if so whether it is empty.
|
|
*
|
|
* This function is used for checking both the data directory
|
|
* and tablespace directories.
|
|
*/
|
|
DataDirState
|
|
check_dir(const char *path)
|
|
{
|
|
DIR *chkdir = NULL;
|
|
struct dirent *file = NULL;
|
|
int result = DIR_EMPTY;
|
|
|
|
errno = 0;
|
|
|
|
chkdir = opendir(path);
|
|
|
|
if (!chkdir)
|
|
return (errno == ENOENT) ? DIR_NOENT : DIR_ERROR;
|
|
|
|
while ((file = readdir(chkdir)) != NULL)
|
|
{
|
|
if (strcmp(".", file->d_name) == 0 ||
|
|
strcmp("..", file->d_name) == 0)
|
|
{
|
|
/* skip this and parent directory */
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
result = DIR_NOT_EMPTY;
|
|
break;
|
|
}
|
|
}
|
|
|
|
closedir(chkdir);
|
|
|
|
if (errno != 0)
|
|
return DIR_ERROR; /* some kind of I/O error? */
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create directory with error log message when failing
|
|
*/
|
|
bool
|
|
create_dir(const char *path)
|
|
{
|
|
char create_dir_path[MAXPGPATH];
|
|
|
|
/* mkdir_p() may modify the supplied path */
|
|
strncpy(create_dir_path, path, MAXPGPATH);
|
|
|
|
if (mkdir_p(create_dir_path, 0700) == 0)
|
|
return true;
|
|
|
|
log_error(_("unable to create directory \"%s\""), create_dir_path);
|
|
log_detail("%s", strerror(errno));
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
set_dir_permissions(const char *path)
|
|
{
|
|
return (chmod(path, 0700) != 0) ? false : true;
|
|
}
|
|
|
|
|
|
/* function from initdb.c */
|
|
/* source adapted from FreeBSD /src/bin/mkdir/mkdir.c */
|
|
|
|
/*
|
|
* this tries to build all the elements of a path to a directory a la mkdir -p
|
|
* we assume the path is in canonical form, i.e. uses / as the separator
|
|
* we also assume it isn't null.
|
|
*
|
|
* note that on failure, the path arg has been modified to show the particular
|
|
* directory level we had problems with.
|
|
*/
|
|
int
|
|
mkdir_p(char *path, mode_t omode)
|
|
{
|
|
struct stat sb;
|
|
mode_t numask,
|
|
oumask;
|
|
int first,
|
|
last,
|
|
retval;
|
|
char *p;
|
|
|
|
p = path;
|
|
oumask = 0;
|
|
retval = 0;
|
|
|
|
|
|
if (p[0] == '/') /* Skip leading '/'. */
|
|
++p;
|
|
for (first = 1, last = 0; !last; ++p)
|
|
{
|
|
if (p[0] == '\0')
|
|
last = 1;
|
|
else if (p[0] != '/')
|
|
continue;
|
|
*p = '\0';
|
|
if (!last && p[1] == '\0')
|
|
last = 1;
|
|
if (first)
|
|
{
|
|
/*
|
|
* POSIX 1003.2: For each dir operand that does not name an
|
|
* existing directory, effects equivalent to those caused by the
|
|
* following command shall occcur:
|
|
*
|
|
* mkdir -p -m $(umask -S),u+wx $(dirname dir) && mkdir [-m mode]
|
|
* dir
|
|
*
|
|
* We change the user's umask and then restore it, instead of
|
|
* doing chmod's.
|
|
*/
|
|
oumask = umask(0);
|
|
numask = oumask & ~(S_IWUSR | S_IXUSR);
|
|
(void) umask(numask);
|
|
first = 0;
|
|
}
|
|
if (last)
|
|
(void) umask(oumask);
|
|
|
|
/* check for pre-existing directory; ok if it's a parent */
|
|
if (stat(path, &sb) == 0)
|
|
{
|
|
if (!S_ISDIR(sb.st_mode))
|
|
{
|
|
if (last)
|
|
errno = EEXIST;
|
|
else
|
|
errno = ENOTDIR;
|
|
retval = 1;
|
|
break;
|
|
}
|
|
}
|
|
else if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0)
|
|
{
|
|
retval = 1;
|
|
break;
|
|
}
|
|
if (!last)
|
|
*p = '/';
|
|
}
|
|
if (!first && !last)
|
|
(void) umask(oumask);
|
|
return retval;
|
|
}
|
|
|
|
|
|
bool
|
|
is_pg_dir(const char *path)
|
|
{
|
|
char dirpath[MAXPGPATH] = "";
|
|
struct stat sb;
|
|
|
|
/* test pgdata */
|
|
snprintf(dirpath, MAXPGPATH, "%s/PG_VERSION", path);
|
|
if (stat(dirpath, &sb) == 0)
|
|
return true;
|
|
|
|
/* TODO: sanity check other files */
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Attempt to determine if a PostgreSQL data directory is in use
|
|
* by reading the pidfile. This is the same mechanism used by
|
|
* "pg_ctl".
|
|
*
|
|
* This function will abort with appropriate log messages if a file error
|
|
* is encountered, as the user will need to address the situation before
|
|
* any further useful progress can be made.
|
|
*/
|
|
PgDirState
|
|
is_pg_running(const char *path)
|
|
{
|
|
long pid;
|
|
FILE *pidf;
|
|
|
|
char pid_file[MAXPGPATH];
|
|
|
|
/* it's reasonable to assume the pidfile name will not change */
|
|
snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", path);
|
|
|
|
pidf = fopen(pid_file, "r");
|
|
if (pidf == NULL)
|
|
{
|
|
/*
|
|
* No PID file - PostgreSQL shouldn't be running. From 9.3 (the
|
|
* earliesty version we care about) removal of the PID file will
|
|
* cause the postmaster to shut down, so it's highly unlikely
|
|
* that PostgreSQL will still be running.
|
|
*/
|
|
if (errno == ENOENT)
|
|
{
|
|
return PG_DIR_NOT_RUNNING;
|
|
}
|
|
else
|
|
{
|
|
log_error(_("unable to open PostgreSQL PID file \"%s\""), pid_file);
|
|
log_detail("%s", strerror(errno));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* In the unlikely event we're unable to extract a PID from the PID file,
|
|
* log a warning but assume we're not dealing with a running instance
|
|
* as PostgreSQL should have shut itself down in these cases anyway.
|
|
*/
|
|
if (fscanf(pidf, "%ld", &pid) != 1)
|
|
{
|
|
/* Is the file empty? */
|
|
if (ftell(pidf) == 0 && feof(pidf))
|
|
{
|
|
log_warning(_("PostgreSQL PID file \"%s\" is empty"), path);
|
|
}
|
|
else
|
|
{
|
|
log_warning(_("invalid data in PostgreSQL PID file \"%s\""), path);
|
|
}
|
|
|
|
fclose(pidf);
|
|
|
|
return PG_DIR_NOT_RUNNING;
|
|
}
|
|
|
|
fclose(pidf);
|
|
|
|
if (pid == getpid())
|
|
return PG_DIR_NOT_RUNNING;
|
|
|
|
if (pid == getppid())
|
|
return PG_DIR_NOT_RUNNING;
|
|
|
|
if (kill(pid, 0) == 0)
|
|
return PG_DIR_RUNNING;
|
|
|
|
return PG_DIR_NOT_RUNNING;
|
|
}
|
|
|
|
|
|
bool
|
|
create_pg_dir(const char *path, bool force)
|
|
{
|
|
/* Check this directory can be used as a PGDATA dir */
|
|
switch (check_dir(path))
|
|
{
|
|
case DIR_NOENT:
|
|
/* directory does not exist, attempt to create it */
|
|
log_info(_("creating directory \"%s\"..."), path);
|
|
|
|
if (!create_dir(path))
|
|
{
|
|
log_error(_("unable to create directory \"%s\"..."),
|
|
path);
|
|
return false;
|
|
}
|
|
break;
|
|
case DIR_EMPTY:
|
|
/* exists but empty, fix permissions and use it */
|
|
log_info(_("checking and correcting permissions on existing directory \"%s\""),
|
|
path);
|
|
|
|
if (!set_dir_permissions(path))
|
|
{
|
|
log_error(_("unable to change permissions of directory \"%s\""), path);
|
|
log_detail("%s", strerror(errno));
|
|
return false;
|
|
}
|
|
break;
|
|
case DIR_NOT_EMPTY:
|
|
/* exists but is not empty */
|
|
log_warning(_("directory \"%s\" exists but is not empty"),
|
|
path);
|
|
|
|
if (is_pg_dir(path))
|
|
{
|
|
if (force == true)
|
|
{
|
|
log_notice(_("-F/--force provided - deleting existing data directory \"%s\""), path);
|
|
nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
|
|
|
|
/* recreate the directory ourselves to ensure permissions are correct */
|
|
if (!create_dir(path))
|
|
{
|
|
log_error(_("unable to create directory \"%s\"..."),
|
|
path);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (force == true)
|
|
{
|
|
log_notice(_("deleting existing directory \"%s\""), path);
|
|
nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
|
|
|
|
/* recreate the directory ourselves to ensure permissions are correct */
|
|
if (!create_dir(path))
|
|
{
|
|
log_error(_("unable to create directory \"%s\"..."),
|
|
path);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
break;
|
|
case DIR_ERROR:
|
|
log_error(_("could not access directory \"%s\"")
|
|
, path);
|
|
log_detail("%s", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
rmdir_recursive(const char *path)
|
|
{
|
|
return nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
|
|
}
|
|
|
|
static int
|
|
unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
|
|
{
|
|
int rv = remove(fpath);
|
|
|
|
if (rv)
|
|
perror(fpath);
|
|
|
|
return rv;
|
|
}
|