mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-23 07:06:30 +00:00
No need to prefix each line with the program name; this was pretty inconsistent anyway. The only place where log output needs to identify the outputting program is when syslog is being used, which is done anyway.
309 lines
6.2 KiB
C
309 lines
6.2 KiB
C
/*
|
|
* check_dir.c - Directories management functions
|
|
* Copyright (C) 2ndQuadrant, 2010-2015
|
|
*
|
|
* 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 <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
/* NB: postgres_fe must be included BEFORE check_dir */
|
|
#include "postgres_fe.h"
|
|
#include "check_dir.h"
|
|
|
|
#include "strutil.h"
|
|
#include "log.h"
|
|
|
|
/*
|
|
* 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 *dir)
|
|
{
|
|
DIR *chkdir;
|
|
struct dirent *file;
|
|
int result = 1;
|
|
|
|
errno = 0;
|
|
|
|
chkdir = opendir(dir);
|
|
|
|
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 *dir)
|
|
{
|
|
if (mkdir_p(dir, 0700) == 0)
|
|
return true;
|
|
|
|
log_err(_("unable to create directory \"%s\": %s\n"),
|
|
dir, strerror(errno));
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
set_dir_permissions(char *dir)
|
|
{
|
|
return (chmod(dir, 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 *dir)
|
|
{
|
|
const size_t buf_sz = 8192;
|
|
char path[buf_sz];
|
|
struct stat sb;
|
|
int r;
|
|
|
|
/* test pgdata */
|
|
xsnprintf(path, buf_sz, "%s/PG_VERSION", dir);
|
|
if (stat(path, &sb) == 0)
|
|
return true;
|
|
|
|
/* test tablespace dir */
|
|
sprintf(path, "ls %s/PG_*/ -I*", dir);
|
|
r = system(path);
|
|
if (r == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
create_pg_dir(char *dir, bool force)
|
|
{
|
|
bool pg_dir = false;
|
|
|
|
/* Check this directory could be used as a PGDATA dir */
|
|
switch (check_dir(dir))
|
|
{
|
|
case 0:
|
|
/* dir not there, must create it */
|
|
log_info(_("creating directory \"%s\"...\n"), dir);
|
|
|
|
if (!create_dir(dir))
|
|
{
|
|
log_err(_("unable to create directory \"%s\"...\n"),
|
|
dir);
|
|
return false;
|
|
}
|
|
break;
|
|
case 1:
|
|
/* Present but empty, fix permissions and use it */
|
|
log_info(_("checking and correcting permissions on existing directory %s ...\n"),
|
|
dir);
|
|
|
|
if (!set_dir_permissions(dir))
|
|
{
|
|
log_err(_("unable to change permissions of directory \"%s\": %s\n"),
|
|
dir, strerror(errno));
|
|
return false;
|
|
}
|
|
break;
|
|
case 2:
|
|
/* Present and not empty */
|
|
log_warning(_("directory \"%s\" exists but is not empty\n"),
|
|
dir);
|
|
|
|
pg_dir = is_pg_dir(dir);
|
|
|
|
/*
|
|
* we use force to reduce the time needed to restore a node which
|
|
* turn async after a failover or anything else
|
|
*/
|
|
if (pg_dir && force)
|
|
{
|
|
/* Let it continue */
|
|
break;
|
|
}
|
|
else if (pg_dir && !force)
|
|
{
|
|
log_warning(_("\nThis 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 --force option\n"));
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
default:
|
|
/* Trouble accessing directory */
|
|
log_err(_("could not access directory \"%s\": %s\n"),
|
|
dir, strerror(errno));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|