mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-22 22:56:29 +00:00
Additional "standby clone" code
We'll break up the unwieldy "do_standby_clone()" function into discrete unit for easier maintenance.
This commit is contained in:
341
dirutil.c
Normal file
341
dirutil.c
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
*
|
||||
* dirmod.c
|
||||
* directory handling functions
|
||||
*
|
||||
* Copyright (c) 2ndQuadrant, 2010-2017
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <dirent.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"
|
||||
|
||||
|
||||
static bool _create_pg_dir(char *dir, bool force, bool for_witness);
|
||||
static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* make sure the directory either doesn't exist or 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
|
||||
*
|
||||
* Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty,
|
||||
* or -1 if trouble accessing directory
|
||||
*/
|
||||
int
|
||||
check_dir(char *path)
|
||||
{
|
||||
DIR *chkdir;
|
||||
struct dirent *file;
|
||||
int result = 1;
|
||||
|
||||
errno = 0;
|
||||
|
||||
chkdir = opendir(path);
|
||||
|
||||
if (!chkdir)
|
||||
return (errno == ENOENT) ? 0 : -1;
|
||||
|
||||
while ((file = readdir(chkdir)) != NULL)
|
||||
{
|
||||
if (strcmp(".", file->d_name) == 0 ||
|
||||
strcmp("..", file->d_name) == 0)
|
||||
{
|
||||
/* skip this and parent directory */
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = 2; /* not empty */
|
||||
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);
|
||||
|
||||
if (errno != 0)
|
||||
return -1; /* some kind of I/O error? */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create directory with error log message when failing
|
||||
*/
|
||||
bool
|
||||
create_dir(char *path)
|
||||
{
|
||||
if (mkdir_p(path, 0700) == 0)
|
||||
return true;
|
||||
|
||||
log_error(_("unable to create directory \"%s\": %s"),
|
||||
path, strerror(errno));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
set_dir_permissions(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;
|
||||
|
||||
#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 '/'. */
|
||||
++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(char *path)
|
||||
{
|
||||
const size_t buf_sz = 8192;
|
||||
char dirpath[buf_sz];
|
||||
struct stat sb;
|
||||
int r;
|
||||
|
||||
/* test pgdata */
|
||||
snprintf(dirpath, buf_sz, "%s/PG_VERSION", path);
|
||||
if (stat(dirpath, &sb) == 0)
|
||||
return true;
|
||||
|
||||
/* test tablespace dir */
|
||||
sprintf(dirpath, "ls %s/PG_*/ -I*", path);
|
||||
r = system(dirpath);
|
||||
if (r == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
create_pg_dir(char *path, bool force)
|
||||
{
|
||||
return _create_pg_dir(path, force, false);
|
||||
}
|
||||
|
||||
bool
|
||||
create_witness_pg_dir(char *path, bool force)
|
||||
{
|
||||
return _create_pg_dir(path, force, true);
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
_create_pg_dir(char *path, bool force, bool for_witness)
|
||||
{
|
||||
bool pg_dir = false;
|
||||
|
||||
/* Check this directory could be used as a PGDATA dir */
|
||||
switch (check_dir(path))
|
||||
{
|
||||
case 0:
|
||||
/* dir not there, must create it */
|
||||
log_info(_("creating directory \"%s\"...\n"), path);
|
||||
|
||||
if (!create_dir(path))
|
||||
{
|
||||
log_error(_("unable to create directory \"%s\"..."),
|
||||
path);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
/* Present but empty, fix permissions and use it */
|
||||
log_info(_("checking and correcting permissions on existing directory %s ...\n"),
|
||||
path);
|
||||
|
||||
if (!set_dir_permissions(path))
|
||||
{
|
||||
log_error(_("unable to change permissions of directory \"%s\": %s"),
|
||||
path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
/* Present and not empty */
|
||||
log_warning(_("directory \"%s\" exists but is not empty\n"),
|
||||
path);
|
||||
|
||||
pg_dir = is_pg_dir(path);
|
||||
|
||||
|
||||
if (pg_dir && force)
|
||||
{
|
||||
|
||||
/*
|
||||
* The witness server does not store any data other than a copy of the
|
||||
* repmgr metadata, so in --force mode we can simply overwrite the
|
||||
* directory.
|
||||
*
|
||||
* For non-witness servers, we'll leave the data in place, both to reduce
|
||||
* the risk of unintentional data loss and to make it possible for the
|
||||
* data directory to be brought up-to-date with rsync.
|
||||
*/
|
||||
if (for_witness)
|
||||
{
|
||||
log_notice(_("deleting existing data directory \"%s\""), path);
|
||||
nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
|
||||
}
|
||||
/* Let it continue */
|
||||
break;
|
||||
}
|
||||
else if (pg_dir && !force)
|
||||
{
|
||||
log_hint(_("This looks like a PostgreSQL directory.\n"
|
||||
"If you are sure you want to clone here, "
|
||||
"please check there is no PostgreSQL server "
|
||||
"running and use the -F/--force option\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
default:
|
||||
log_error(_("could not access directory \"%s\": %s"),
|
||||
path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user