mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-25 16:16:29 +00:00
"standby clone": cowardly refuse to clone into an active data directory
By checking the PID file in the same way pg_ctl does, we can be pretty much certain whether the target data directory contains an active PostgreSQL instance.
This commit is contained in:
162
dirutil.c
162
dirutil.c
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -38,31 +39,29 @@
|
|||||||
|
|
||||||
static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
|
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;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* make sure the directory either doesn't exist or is empty
|
* Check if a directory exists, and if so whether it is empty.
|
||||||
* we use this function to check the new data directory and
|
|
||||||
* the directories for tablespaces
|
|
||||||
*
|
*
|
||||||
* This is the same check initdb does on the new PGDATA dir
|
* This function is used for checking both the data directory
|
||||||
*
|
* and tablespace directories.
|
||||||
* Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty,
|
|
||||||
* or -1 if trouble accessing directory
|
|
||||||
*/
|
*/
|
||||||
int
|
DataDirState
|
||||||
check_dir(char *path)
|
check_dir(char *path)
|
||||||
{
|
{
|
||||||
DIR *chkdir;
|
DIR *chkdir = NULL;
|
||||||
struct dirent *file;
|
struct dirent *file = NULL;
|
||||||
int result = 1;
|
int result = DIR_EMPTY;
|
||||||
|
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
|
||||||
chkdir = opendir(path);
|
chkdir = opendir(path);
|
||||||
|
|
||||||
if (!chkdir)
|
if (!chkdir)
|
||||||
return (errno == ENOENT) ? 0 : -1;
|
return (errno == ENOENT) ? DIR_NOENT : DIR_ERROR;
|
||||||
|
|
||||||
while ((file = readdir(chkdir)) != NULL)
|
while ((file = readdir(chkdir)) != NULL)
|
||||||
{
|
{
|
||||||
@@ -74,25 +73,15 @@ check_dir(char *path)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = 2; /* not empty */
|
result = DIR_NOT_EMPTY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4), but not in
|
|
||||||
* released version
|
|
||||||
*/
|
|
||||||
if (GetLastError() == ERROR_NO_MORE_FILES)
|
|
||||||
errno = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
closedir(chkdir);
|
closedir(chkdir);
|
||||||
|
|
||||||
if (errno != 0)
|
if (errno != 0)
|
||||||
return -1; /* some kind of I/O error? */
|
return DIR_ERROR; /* some kind of I/O error? */
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -107,12 +96,13 @@ create_dir(char *path)
|
|||||||
if (mkdir_p(path, 0700) == 0)
|
if (mkdir_p(path, 0700) == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
log_error(_("unable to create directory \"%s\": %s"),
|
log_error(_("unable to create directory \"%s\""), path);
|
||||||
path, strerror(errno));
|
log_detail("%s", strerror(errno));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
set_dir_permissions(char *path)
|
set_dir_permissions(char *path)
|
||||||
{
|
{
|
||||||
@@ -147,26 +137,6 @@ mkdir_p(char *path, mode_t omode)
|
|||||||
oumask = 0;
|
oumask = 0;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
/* skip network and drive specifiers for win32 */
|
|
||||||
if (strlen(p) >= 2)
|
|
||||||
{
|
|
||||||
if (p[0] == '/' && p[1] == '/')
|
|
||||||
{
|
|
||||||
/* network drive */
|
|
||||||
p = strstr(p + 2, "/");
|
|
||||||
if (p == NULL)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (p[1] == ':' &&
|
|
||||||
((p[0] >= 'a' && p[0] <= 'z') ||
|
|
||||||
(p[0] >= 'A' && p[0] <= 'Z')))
|
|
||||||
{
|
|
||||||
/* local drive */
|
|
||||||
p += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (p[0] == '/') /* Skip leading '/'. */
|
if (p[0] == '/') /* Skip leading '/'. */
|
||||||
++p;
|
++p;
|
||||||
@@ -243,15 +213,91 @@ is_pg_dir(char *path)
|
|||||||
return false;
|
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(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
bool
|
||||||
create_pg_dir(char *path, bool force)
|
create_pg_dir(char *path, bool force)
|
||||||
{
|
{
|
||||||
/* Check this directory could be used as a PGDATA dir */
|
/* Check this directory can be used as a PGDATA dir */
|
||||||
switch (check_dir(path))
|
switch (check_dir(path))
|
||||||
{
|
{
|
||||||
case 0:
|
case DIR_NOENT:
|
||||||
/* dir not there, must create it */
|
/* directory does not exist, attempt to create it */
|
||||||
log_info(_("creating directory \"%s\"..."), path);
|
log_info(_("creating directory \"%s\"..."), path);
|
||||||
|
|
||||||
if (!create_dir(path))
|
if (!create_dir(path))
|
||||||
@@ -261,20 +307,20 @@ create_pg_dir(char *path, bool force)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1:
|
case DIR_EMPTY:
|
||||||
/* Present but empty, fix permissions and use it */
|
/* exists but empty, fix permissions and use it */
|
||||||
log_info(_("checking and correcting permissions on existing directory %s"),
|
log_info(_("checking and correcting permissions on existing directory \"%s\""),
|
||||||
path);
|
path);
|
||||||
|
|
||||||
if (!set_dir_permissions(path))
|
if (!set_dir_permissions(path))
|
||||||
{
|
{
|
||||||
log_error(_("unable to change permissions of directory \"%s\":\n %s"),
|
log_error(_("unable to change permissions of directory \"%s\""), path);
|
||||||
path, strerror(errno));
|
log_detail("%s", strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case DIR_NOT_EMPTY:
|
||||||
/* Present and not empty */
|
/* exists but is not empty */
|
||||||
log_warning(_("directory \"%s\" exists but is not empty"),
|
log_warning(_("directory \"%s\" exists but is not empty"),
|
||||||
path);
|
path);
|
||||||
|
|
||||||
@@ -282,7 +328,7 @@ create_pg_dir(char *path, bool force)
|
|||||||
{
|
{
|
||||||
if (force == true)
|
if (force == true)
|
||||||
{
|
{
|
||||||
log_notice(_("deleting existing data directory \"%s\""), path);
|
log_notice(_("-F/--force provided - deleting existing data directory \"%s\""), path);
|
||||||
nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
|
nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -300,7 +346,7 @@ create_pg_dir(char *path, bool force)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
case DIR_ERROR:
|
||||||
log_error(_("could not access directory \"%s\": %s"),
|
log_error(_("could not access directory \"%s\": %s"),
|
||||||
path, strerror(errno));
|
path, strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
19
dirutil.h
19
dirutil.h
@@ -19,12 +19,29 @@
|
|||||||
#ifndef _DIRUTIL_H_
|
#ifndef _DIRUTIL_H_
|
||||||
#define _DIRUTIL_H_
|
#define _DIRUTIL_H_
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
DIR_ERROR = -1,
|
||||||
|
DIR_NOENT,
|
||||||
|
DIR_EMPTY,
|
||||||
|
DIR_NOT_EMPTY
|
||||||
|
} DataDirState;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
PG_DIR_ERROR = -1,
|
||||||
|
PG_DIR_NOT_RUNNING,
|
||||||
|
PG_DIR_RUNNING
|
||||||
|
} PgDirState;
|
||||||
|
|
||||||
extern int mkdir_p(char *path, mode_t omode);
|
extern int mkdir_p(char *path, mode_t omode);
|
||||||
extern bool set_dir_permissions(char *path);
|
extern bool set_dir_permissions(char *path);
|
||||||
|
|
||||||
extern int check_dir(char *path);
|
extern DataDirState check_dir(char *path);
|
||||||
extern bool create_dir(char *path);
|
extern bool create_dir(char *path);
|
||||||
extern bool is_pg_dir(char *path);
|
extern bool is_pg_dir(char *path);
|
||||||
|
extern PgDirState is_pg_running(char *path);
|
||||||
extern bool create_pg_dir(char *path, bool force);
|
extern bool create_pg_dir(char *path, bool force);
|
||||||
extern int rmdir_recursive(char *path);
|
extern int rmdir_recursive(char *path);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -220,6 +220,49 @@ do_standby_clone(void)
|
|||||||
param_set(&recovery_conninfo, "application_name", "repmgr");
|
param_set(&recovery_conninfo, "application_name", "repmgr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do some sanity checks on the proposed data directory; if it exists:
|
||||||
|
* - check it's openable
|
||||||
|
* - check if there's an instance running
|
||||||
|
*
|
||||||
|
* We do this here so the check can be part of a --dry-run.
|
||||||
|
*/
|
||||||
|
switch (check_dir(local_data_directory))
|
||||||
|
{
|
||||||
|
case DIR_ERROR:
|
||||||
|
log_error(_("unable to access specified data directory \"%s\""), local_data_directory);
|
||||||
|
log_detail("%s", strerror(errno));
|
||||||
|
exit(ERR_BAD_CONFIG);
|
||||||
|
break;
|
||||||
|
case DIR_NOENT:
|
||||||
|
/*
|
||||||
|
* directory doesn't exist
|
||||||
|
* TODO: in --dry-run mode, attempt to create and delete?
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
case DIR_EMPTY:
|
||||||
|
/* Present but empty */
|
||||||
|
break;
|
||||||
|
case DIR_NOT_EMPTY:
|
||||||
|
/* Present but not empty */
|
||||||
|
if (is_pg_dir(local_data_directory))
|
||||||
|
{
|
||||||
|
/* even -F/--force is not enough to overwrite an active directory... */
|
||||||
|
if (is_pg_running(local_data_directory))
|
||||||
|
{
|
||||||
|
log_error(_("specified data directory \"%s\" appears to contain a running PostgreSQL instance"),
|
||||||
|
local_data_directory);
|
||||||
|
log_hint(_("ensure the target data directory does not contain a running PostgreSQL instance"));
|
||||||
|
exit(ERR_BAD_CONFIG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* By default attempt to connect to the source node. This will fail if no
|
* By default attempt to connect to the source node. This will fail if no
|
||||||
* connection is possible, unless in Barman mode, in which case we can
|
* connection is possible, unless in Barman mode, in which case we can
|
||||||
@@ -279,6 +322,7 @@ do_standby_clone(void)
|
|||||||
exit(ERR_BAD_CONFIG);
|
exit(ERR_BAD_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (upstream_conninfo_found == true)
|
if (upstream_conninfo_found == true)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -451,6 +495,7 @@ do_standby_clone(void)
|
|||||||
PQfinish(superuser_conn);
|
PQfinish(superuser_conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (runtime_options.dry_run == true)
|
if (runtime_options.dry_run == true)
|
||||||
{
|
{
|
||||||
if (upstream_node_id != UNKNOWN_NODE_ID)
|
if (upstream_node_id != UNKNOWN_NODE_ID)
|
||||||
|
|||||||
Reference in New Issue
Block a user