/* * check_dir.c - Directories management functions * Copyright (c) 2ndQuadrant, 2010-2017 * * 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 . * */ #include #include #include #include #include #include #include /* NB: postgres_fe must be included BEFORE check_dir */ #include #include #include "check_dir.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 *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) { return _create_pg_dir(dir, force, false); } bool create_witness_pg_dir(char *dir, bool force) { return _create_pg_dir(dir, force, true); } static bool _create_pg_dir(char *dir, bool force, bool for_witness) { 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); 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\"\n"), dir); nftw(dir, 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: /* Trouble accessing directory */ log_err(_("could not access directory \"%s\": %s\n"), dir, 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; }