1
0
mirror of https://github.com/postgrespro/pg_probackup.git synced 2025-01-24 11:46:31 +02:00
pg_probackup/dir.c
Michael Paquier f94c5ab447 Sanitize logging facility
--debug and --verbose had actually the same meaning as they were aimed
at giving to the user information regarding how the process is running,
hence both options are merged into --verbose and use elog(LOG) to decide
if a given message should be printed out depending on the verbosity of
the call. This makes a couple of routines more readable as they do not
depend on any boolean checks.

The "_()" have been removed from the code, those are aimed at being used
for translation but having them mandatorily in each log message is just
useless noise. If needed, pgut.c should be updated in consequence to
have a more portable facility.

At the same time this commit takes care of putting into correct shape
some code paths more in-line with PostgreSQL policy. There are surely
more of this kind of ugly stuff but at this stage things are more simple
and more manageable.
2016-01-14 16:36:39 +09:00

637 lines
14 KiB
C

/*-------------------------------------------------------------------------
*
* dir.c: directory operation utility.
*
* Copyright (c) 2009-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_arman.h"
#include <libgen.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <time.h>
#include "pgut/pgut-port.h"
/* directory exclusion list for backup mode listing */
const char *pgdata_exclude[] =
{
"pg_xlog",
"pg_stat_tmp",
"pgsql_tmp",
NULL, /* arclog_path will be set later */
NULL, /* 'pg_tblspc' will be set later */
NULL, /* sentinel */
};
static pgFile *pgFileNew(const char *path, bool omit_symlink);
static int BlackListCompare(const void *str1, const void *str2);
/* create directory, also create parent directories if necessary */
int
dir_create_dir(const char *dir, mode_t mode)
{
char copy[MAXPGPATH];
char *parent;
strncpy(copy, dir, MAXPGPATH);
parent = dirname(copy);
/* Create parent first */
if (access(parent, F_OK) == -1)
dir_create_dir(parent, mode);
/* Create directory */
#ifdef __darwin__
if (mkdir(copy, mode) == -1)
#else
if (mkdir(dir, mode) == -1)
#endif
{
if (errno == EEXIST) /* already exist */
return 0;
elog(ERROR_SYSTEM, "cannot create directory \"%s\": %s", dir,
strerror(errno));
}
return 0;
}
static pgFile *
pgFileNew(const char *path, bool omit_symlink)
{
struct stat st;
pgFile *file;
/* stat the file */
if ((omit_symlink ? stat(path, &st) : lstat(path, &st)) == -1)
{
/* file not found is not an error case */
if (errno == ENOENT)
return NULL;
elog(ERROR_SYSTEM, "cannot stat file \"%s\": %s", path,
strerror(errno));
}
file = (pgFile *) pgut_malloc(offsetof(pgFile, path) + strlen(path) + 1);
file->mtime = st.st_mtime;
file->size = st.st_size;
file->read_size = 0;
file->write_size = 0;
file->mode = st.st_mode;
file->crc = 0;
file->is_datafile = false;
file->linked = NULL;
strcpy(file->path, path); /* enough buffer size guaranteed */
return file;
}
/*
* Delete file pointed by the pgFile.
* If the pgFile points directory, the directory must be empty.
*/
void
pgFileDelete(pgFile *file)
{
if (S_ISDIR(file->mode))
{
if (rmdir(file->path) == -1)
{
if (errno == ENOENT)
return;
else if (errno == ENOTDIR) /* could be symbolic link */
goto delete_file;
elog(ERROR_SYSTEM, "cannot remove directory \"%s\": %s",
file->path, strerror(errno));
}
return;
}
delete_file:
if (remove(file->path) == -1)
{
if (errno == ENOENT)
return;
elog(ERROR_SYSTEM, "cannot remove file \"%s\": %s", file->path,
strerror(errno));
}
}
pg_crc32
pgFileGetCRC(pgFile *file)
{
FILE *fp;
pg_crc32 crc = 0;
char buf[1024];
size_t len;
int errno_tmp;
/* open file in binary read mode */
fp = fopen(file->path, "r");
if (fp == NULL)
elog(ERROR_SYSTEM, "cannot open file \"%s\": %s",
file->path, strerror(errno));
/* calc CRC of backup file */
INIT_CRC32C(crc);
while ((len = fread(buf, 1, sizeof(buf), fp)) == sizeof(buf))
{
if (interrupted)
elog(ERROR_INTERRUPTED, "interrupted during CRC calculation");
COMP_CRC32C(crc, buf, len);
}
errno_tmp = errno;
if (!feof(fp))
elog(WARNING, "cannot read \"%s\": %s", file->path,
strerror(errno_tmp));
if (len > 0)
COMP_CRC32C(crc, buf, len);
FIN_CRC32C(crc);
fclose(fp);
return crc;
}
void
pgFileFree(void *file)
{
if (file == NULL)
return;
free(((pgFile *)file)->linked);
free(file);
}
/* Compare two pgFile with their path in ascending order of ASCII code. */
int
pgFileComparePath(const void *f1, const void *f2)
{
pgFile *f1p = *(pgFile **)f1;
pgFile *f2p = *(pgFile **)f2;
return strcmp(f1p->path, f2p->path);
}
/* Compare two pgFile with their path in descending order of ASCII code. */
int
pgFileComparePathDesc(const void *f1, const void *f2)
{
return -pgFileComparePath(f1, f2);
}
/* Compare two pgFile with their modify timestamp. */
int
pgFileCompareMtime(const void *f1, const void *f2)
{
pgFile *f1p = *(pgFile **)f1;
pgFile *f2p = *(pgFile **)f2;
if (f1p->mtime > f2p->mtime)
return 1;
else if (f1p->mtime < f2p->mtime)
return -1;
else
return 0;
}
/* Compare two pgFile with their modify timestamp in descending order. */
int
pgFileCompareMtimeDesc(const void *f1, const void *f2)
{
return -pgFileCompareMtime(f1, f2);
}
static int
BlackListCompare(const void *str1, const void *str2)
{
return strcmp(*(char **) str1, *(char **) str2);
}
/*
* List files, symbolic links and directories in the directory "root" and add
* pgFile objects to "files". We add "root" to "files" if add_root is true.
*
* If the sub-directory name is in "exclude" list, the sub-directory itself is
* listed but the contents of the sub-directory is ignored.
*
* When omit_symlink is true, symbolic link is ignored and only file or
* directory llnked to will be listed.
*/
void
dir_list_file(parray *files, const char *root, const char *exclude[], bool omit_symlink, bool add_root)
{
char path[MAXPGPATH];
char buf[MAXPGPATH * 2];
char black_item[MAXPGPATH * 2];
parray *black_list = NULL;
join_path_components(path, backup_path, PG_BLACK_LIST);
if (root && pgdata && strcmp(root, pgdata) == 0 &&
fileExists(path))
{
FILE *black_list_file = NULL;
black_list = parray_new();
black_list_file = fopen(path, "r");
if (black_list_file == NULL)
elog(ERROR_SYSTEM, "cannot open black_list: %s",
strerror(errno));
while (fgets(buf, lengthof(buf), black_list_file) != NULL)
{
join_path_components(black_item, pgdata, buf);
if (black_item[strlen(black_item) - 1] == '\n')
black_item[strlen(black_item) - 1] = '\0';
if (black_item[0] == '#' || black_item[0] == '\0')
continue;
parray_append(black_list, black_item);
}
fclose(black_list_file);
parray_qsort(black_list, BlackListCompare);
dir_list_file_internal(files, root, exclude, omit_symlink, add_root, black_list);
}
else
dir_list_file_internal(files, root, exclude, omit_symlink, add_root, NULL);
}
void
dir_list_file_internal(parray *files, const char *root, const char *exclude[],
bool omit_symlink, bool add_root, parray *black_list)
{
pgFile *file;
file = pgFileNew(root, omit_symlink);
if (file == NULL)
return;
/* skip if the file is in black_list defined by user */
if (black_list && parray_bsearch(black_list, root, BlackListCompare))
{
/* found in black_list. skip this item */
return;
}
if (add_root)
parray_append(files, file);
/* chase symbolic link chain and find regular file or directory */
while (S_ISLNK(file->mode))
{
ssize_t len;
char linked[MAXPGPATH];
len = readlink(file->path, linked, sizeof(linked));
if (len == -1)
{
elog(ERROR_SYSTEM, "cannot read link \"%s\": %s", file->path,
strerror(errno));
}
linked[len] = '\0';
file->linked = pgut_strdup(linked);
/* make absolute path to read linked file */
if (linked[0] != '/')
{
char dname[MAXPGPATH];
char absolute[MAXPGPATH];
strncpy(dname, file->path, lengthof(dname));
join_path_components(absolute, dname, linked);
file = pgFileNew(absolute, omit_symlink);
}
else
file = pgFileNew(file->linked, omit_symlink);
/* linked file is not found, stop following link chain */
if (file == NULL)
return;
parray_append(files, file);
}
/*
* If the entry was a directory, add it to the list and add call this
* function recursivelly.
* If the directory name is in the exclude list, do not list the contents.
*/
while (S_ISDIR(file->mode))
{
int i;
bool skip = false;
DIR *dir;
struct dirent *dent;
char *dirname;
/* skip entry which matches exclude list */
dirname = strrchr(file->path, '/');
if (dirname == NULL)
dirname = file->path;
else
dirname++;
/*
* If the item in the exclude list starts with '/', compare to the
* absolute path of the directory. Otherwise compare to the directory
* name portion.
*/
for (i = 0; exclude && exclude[i]; i++)
{
if (exclude[i][0] == '/')
{
if (strcmp(file->path, exclude[i]) == 0)
{
skip = true;
break;
}
}
else
{
if (strcmp(dirname, exclude[i]) == 0)
{
skip = true;
break;
}
}
}
if (skip)
break;
/* open directory and list contents */
dir = opendir(file->path);
if (dir == NULL)
{
if (errno == ENOENT)
{
/* maybe the direcotry was removed */
return;
}
elog(ERROR_SYSTEM, "cannot open directory \"%s\": %s",
file->path, strerror(errno));
}
errno = 0;
while ((dent = readdir(dir)))
{
char child[MAXPGPATH];
/* skip entries point current dir or parent dir */
if (strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0)
continue;
join_path_components(child, file->path, dent->d_name);
dir_list_file_internal(files, child, exclude, omit_symlink, true, black_list);
}
if (errno && errno != ENOENT)
{
int errno_tmp = errno;
closedir(dir);
elog(ERROR_SYSTEM, "cannot read directory \"%s\": %s",
file->path, strerror(errno_tmp));
}
closedir(dir);
break; /* pseudo loop */
}
parray_qsort(files, pgFileComparePath);
}
/* print mkdirs.sh */
void
dir_print_mkdirs_sh(FILE *out, const parray *files, const char *root)
{
int i;
for (i = 0; i < parray_num(files); i++)
{
pgFile *file = (pgFile *) parray_get(files, i);
if (S_ISDIR(file->mode))
{
if (strstr(file->path, root) == file->path) {
fprintf(out, "mkdir -m 700 -p %s\n", file->path + strlen(root)
+ 1);
}
else {
fprintf(out, "mkdir -m 700 -p %s\n", file->path);
}
}
}
fprintf(out, "\n");
for (i = 0; i < parray_num(files); i++)
{
pgFile *file = (pgFile *) parray_get(files, i);
if (S_ISLNK(file->mode))
{
fprintf(out, "rm -f %s\n", file->path + strlen(root) + 1);
fprintf(out, "ln -s %s %s\n", file->linked, file->path + strlen(root) + 1);
}
}
}
/* print file list */
void
dir_print_file_list(FILE *out, const parray *files, const char *root, const char *prefix)
{
int i;
int root_len = 0;
/* calculate length of root directory portion */
if (root)
{
root_len = strlen(root);
if (root[root_len - 1] != '/')
root_len++;
}
/* print each file in the list */
for (i = 0; i < parray_num(files); i++)
{
pgFile *file = (pgFile *)parray_get(files, i);
char path[MAXPGPATH];
char *ptr = file->path;
char type;
/* omit root directory portion */
if (root && strstr(ptr, root) == ptr)
ptr = JoinPathEnd(ptr, root);
/* append prefix if not NULL */
if (prefix)
join_path_components(path, prefix, ptr);
else
strcpy(path, ptr);
if (S_ISREG(file->mode) && file->is_datafile)
type = 'F';
else if (S_ISREG(file->mode) && !file->is_datafile)
type = 'f';
else if (S_ISDIR(file->mode))
type = 'd';
else if (S_ISLNK(file->mode))
type = 'l';
else
type = '?';
fprintf(out, "%s %c %lu %u 0%o", path, type,
(unsigned long) file->write_size,
file->crc, file->mode & (S_IRWXU | S_IRWXG | S_IRWXO));
if (S_ISLNK(file->mode))
fprintf(out, " %s\n", file->linked);
else
{
char timestamp[20];
time2iso(timestamp, 20, file->mtime);
fprintf(out, " %s\n", timestamp);
}
}
}
/*
* Construct parray of pgFile from the file list.
* If root is not NULL, path will be absolute path.
*/
parray *
dir_read_file_list(const char *root, const char *file_txt)
{
FILE *fp;
parray *files;
char buf[MAXPGPATH * 2];
fp = fopen(file_txt, "rt");
if (fp == NULL)
elog(errno == ENOENT ? ERROR_CORRUPTED : ERROR_SYSTEM,
"cannot open \"%s\": %s", file_txt, strerror(errno));
files = parray_new();
while (fgets(buf, lengthof(buf), fp))
{
char path[MAXPGPATH];
char type;
unsigned long write_size;
pg_crc32 crc;
unsigned int mode; /* bit length of mode_t depends on platforms */
struct tm tm;
pgFile *file;
memset(&tm, 0, sizeof(tm));
if (sscanf(buf, "%s %c %lu %u %o %d-%d-%d %d:%d:%d",
path, &type, &write_size, &crc, &mode,
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 11)
{
elog(ERROR_CORRUPTED, "invalid format found in \"%s\"",
file_txt);
}
if (type != 'f' && type != 'F' && type != 'd' && type != 'l')
{
elog(ERROR_CORRUPTED, "invalid type '%c' found in \"%s\"",
type, file_txt);
}
tm.tm_isdst = -1;
file = (pgFile *) pgut_malloc(offsetof(pgFile, path) +
(root ? strlen(root) + 1 : 0) + strlen(path) + 1);
tm.tm_year -= 1900;
tm.tm_mon -= 1;
file->mtime = mktime(&tm);
file->mode = mode |
((type == 'f' || type == 'F') ? S_IFREG :
type == 'd' ? S_IFDIR : type == 'l' ? S_IFLNK : 0);
file->size = 0;
file->read_size = 0;
file->write_size = write_size;
file->crc = crc;
file->is_datafile = (type == 'F' ? true : false);
file->linked = NULL;
if (root)
sprintf(file->path, "%s/%s", root, path);
else
strcpy(file->path, path);
parray_append(files, file);
}
fclose(fp);
/* file.txt is sorted, so this qsort is redundant */
parray_qsort(files, pgFileComparePath);
return files;
}
/* copy contents of directory from_root into to_root */
void
dir_copy_files(const char *from_root, const char *to_root)
{
int i;
parray *files = parray_new();
/* don't copy root directory */
dir_list_file(files, from_root, NULL, true, false);
for (i = 0; i < parray_num(files); i++)
{
pgFile *file = (pgFile *) parray_get(files, i);
if (S_ISDIR(file->mode))
{
char to_path[MAXPGPATH];
join_path_components(to_path, to_root, file->path + strlen(from_root) + 1);
if (verbose && !check)
elog(LOG, "creating directory \"%s\"",
file->path + strlen(from_root) + 1);
if (!check)
dir_create_dir(to_path, DIR_PERMISSION);
continue;
}
else if (S_ISREG(file->mode))
{
if (verbose && !check)
elog(LOG, "copying \"%s\"",
file->path + strlen(from_root) + 1);
if (!check)
copy_file(from_root, to_root, file);
}
}
/* cleanup */
parray_walk(files, pgFileFree);
parray_free(files);
}
#ifdef NOT_USED
void
pgFileDump(pgFile *file, FILE *out)
{
char mtime_str[100];
fprintf(out, "=================\n");
if (file)
{
time2iso(mtime_str, 100, file->mtime);
fprintf(out, "mtime=%lu(%s)\n", file->mtime, mtime_str);
fprintf(out, "size=" UINT64_FORMAT "\n", (uint64)file->size);
fprintf(out, "read_size=" UINT64_FORMAT "\n", (uint64)file->read_size);
fprintf(out, "write_size=" UINT64_FORMAT "\n", (uint64)file->write_size);
fprintf(out, "mode=0%o\n", file->mode);
fprintf(out, "crc=%u\n", file->crc);
fprintf(out, "is_datafile=%s\n", file->is_datafile ? "true" : "false");
fprintf(out, "linked=\"%s\"\n", file->linked ? file->linked : "nil");
fprintf(out, "path=\"%s\"\n", file->path);
}
fprintf(out, "=================\n");
}
#endif