1
0
mirror of https://github.com/postgrespro/pg_probackup.git synced 2024-11-28 09:33:54 +02:00
pg_probackup/catalog.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

558 lines
14 KiB
C

/*-------------------------------------------------------------------------
*
* catalog.c: backup catalog opration
*
* Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_arman.h"
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "pgut/pgut-port.h"
static pgBackup *catalog_read_ini(const char *path);
#define BOOL_TO_STR(val) ((val) ? "true" : "false")
static int lock_fd = -1;
/*
* Lock of the catalog with pg_arman.ini file and return 0.
* If the lock is held by another one, return 1 immediately.
*/
int
catalog_lock(void)
{
int ret;
char id_path[MAXPGPATH];
join_path_components(id_path, backup_path, PG_RMAN_INI_FILE);
lock_fd = open(id_path, O_RDWR);
if (lock_fd == -1)
elog(errno == ENOENT ? ERROR_CORRUPTED : ERROR_SYSTEM,
"cannot open file \"%s\": %s", id_path, strerror(errno));
ret = flock(lock_fd, LOCK_EX | LOCK_NB); /* non-blocking */
if (ret == -1)
{
if (errno == EWOULDBLOCK)
{
close(lock_fd);
return 1;
}
else
{
int errno_tmp = errno;
close(lock_fd);
elog(ERROR_SYSTEM, "cannot lock file \"%s\": %s", id_path,
strerror(errno_tmp));
}
}
return 0;
}
/*
* Release catalog lock.
*/
void
catalog_unlock(void)
{
close(lock_fd);
lock_fd = -1;
}
/*
* Create a pgBackup which taken at timestamp.
* If no backup matches, return NULL.
*/
pgBackup *
catalog_get_backup(time_t timestamp)
{
pgBackup tmp;
char ini_path[MAXPGPATH];
tmp.start_time = timestamp;
pgBackupGetPath(&tmp, ini_path, lengthof(ini_path), BACKUP_INI_FILE);
return catalog_read_ini(ini_path);
}
static bool
IsDir(const char *dirpath, const DIR *dir, const struct dirent *ent)
{
#if defined(DT_DIR)
return ent->d_type == DT_DIR;
#elif defined(_finddata_t)
/* Optimization for VC++ on Windows. */
return (dir->dd_dta.attrib & FILE_ATTRIBUTE_DIRECTORY) != 0;
#else
/* Portable implementation because dirent.d_type is not in POSIX. */
char path[MAXPGPATH];
struct stat st;
strlcpy(path, dirpath, MAXPGPATH);
strlcat(path, "/", MAXPGPATH);
strlcat(path, ent->d_name, MAXPGPATH);
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
#endif
}
/*
* Create list fo backups started between begin and end from backup catalog.
* If range was NULL, all of backup are listed.
* The list is sorted in order of descending start time.
*/
parray *
catalog_get_backup_list(const pgBackupRange *range)
{
const pgBackupRange range_all = { 0, 0 };
DIR *date_dir = NULL;
struct dirent *date_ent = NULL;
DIR *time_dir = NULL;
struct dirent *time_ent = NULL;
char date_path[MAXPGPATH];
parray *backups = NULL;
pgBackup *backup = NULL;
struct tm *tm;
char begin_date[100];
char begin_time[100];
char end_date[100];
char end_time[100];
if (range == NULL)
range = &range_all;
/* make date/time string */
tm = localtime(&range->begin);
strftime(begin_date, lengthof(begin_date), "%Y%m%d", tm);
strftime(begin_time, lengthof(begin_time), "%H%M%S", tm);
tm = localtime(&range->end);
strftime(end_date, lengthof(end_date), "%Y%m%d", tm);
strftime(end_time, lengthof(end_time), "%H%M%S", tm);
/* open backup root directory */
date_dir = opendir(backup_path);
if (date_dir == NULL)
{
elog(WARNING, "cannot open directory \"%s\": %s", backup_path,
strerror(errno));
goto err_proc;
}
/* scan date/time directories and list backups in the range */
backups = parray_new();
for (; (date_ent = readdir(date_dir)) != NULL; errno = 0)
{
/* skip not-directory entries and hidden entries */
if (!IsDir(backup_path, date_dir, date_ent) || date_ent->d_name[0] == '.')
continue;
/* skip online WAL backup directory */
if (strcmp(date_ent->d_name, RESTORE_WORK_DIR) == 0)
continue;
/* If the date is out of range, skip it. */
if (pgBackupRangeIsValid(range) &&
(strcmp(begin_date, date_ent->d_name) > 0 ||
strcmp(end_date, date_ent->d_name) < 0))
continue;
/* open subdirectory (date directory) and search time directory */
join_path_components(date_path, backup_path, date_ent->d_name);
time_dir = opendir(date_path);
if (time_dir == NULL)
{
elog(WARNING, "cannot open directory \"%s\": %s",
date_ent->d_name, strerror(errno));
goto err_proc;
}
for (; (time_ent = readdir(time_dir)) != NULL; errno = 0)
{
char ini_path[MAXPGPATH];
/* skip not-directory and hidden directories */
if (!IsDir(date_path, date_dir, time_ent) || time_ent->d_name[0] == '.')
continue;
/* If the time is out of range, skip it. */
if (pgBackupRangeIsValid(range) &&
(strcmp(begin_time, time_ent->d_name) > 0 ||
strcmp(end_time, time_ent->d_name) < 0))
continue;
/* read backup information from backup.ini */
snprintf(ini_path, MAXPGPATH, "%s/%s/%s", date_path,
time_ent->d_name, BACKUP_INI_FILE);
backup = catalog_read_ini(ini_path);
/* ignore corrupted backup */
if (backup)
{
parray_append(backups, backup);
backup = NULL;
}
}
if (errno && errno != ENOENT)
{
elog(WARNING, "cannot read date directory \"%s\": %s",
date_ent->d_name, strerror(errno));
goto err_proc;
}
closedir(time_dir);
time_dir = NULL;
}
if (errno)
{
elog(WARNING, "cannot read backup root directory \"%s\": %s",
backup_path, strerror(errno));
goto err_proc;
}
closedir(date_dir);
date_dir = NULL;
parray_qsort(backups, pgBackupCompareIdDesc);
return backups;
err_proc:
if (time_dir)
closedir(time_dir);
if (date_dir)
closedir(date_dir);
if (backup)
pgBackupFree(backup);
if (backups)
parray_walk(backups, pgBackupFree);
parray_free(backups);
return NULL;
}
/*
* Find the last completed database backup from the backup list.
*/
pgBackup *
catalog_get_last_data_backup(parray *backup_list, TimeLineID tli)
{
int i;
pgBackup *backup = NULL;
/* backup_list is sorted in order of descending ID */
for (i = 0; i < parray_num(backup_list); i++)
{
backup = (pgBackup *) parray_get(backup_list, i);
/*
* We need completed database backup in the case of a full or
* differential backup on current timeline.
*/
if (backup->status == BACKUP_STATUS_OK &&
backup->tli == tli &&
(backup->backup_mode == BACKUP_MODE_DIFF_PAGE ||
backup->backup_mode == BACKUP_MODE_FULL))
return backup;
}
return NULL;
}
/* create backup directory in $BACKUP_PATH */
int
pgBackupCreateDir(pgBackup *backup)
{
int i;
char path[MAXPGPATH];
char *subdirs[] = { DATABASE_DIR, NULL };
pgBackupGetPath(backup, path, lengthof(path), NULL);
dir_create_dir(path, DIR_PERMISSION);
/* create directories for actual backup files */
for (i = 0; subdirs[i]; i++)
{
pgBackupGetPath(backup, path, lengthof(path), subdirs[i]);
dir_create_dir(path, DIR_PERMISSION);
}
return 0;
}
/*
* Write configuration section of backup.in to stream "out".
*/
void
pgBackupWriteConfigSection(FILE *out, pgBackup *backup)
{
static const char *modes[] = { "", "PAGE", "FULL"};
fprintf(out, "# configuration\n");
fprintf(out, "BACKUP_MODE=%s\n", modes[backup->backup_mode]);
}
/*
* Write result section of backup.in to stream "out".
*/
void
pgBackupWriteResultSection(FILE *out, pgBackup *backup)
{
char timestamp[20];
fprintf(out, "# result\n");
fprintf(out, "TIMELINEID=%d\n", backup->tli);
fprintf(out, "START_LSN=%x/%08x\n",
(uint32) (backup->start_lsn >> 32),
(uint32) backup->start_lsn);
fprintf(out, "STOP_LSN=%x/%08x\n",
(uint32) (backup->stop_lsn >> 32),
(uint32) backup->stop_lsn);
time2iso(timestamp, lengthof(timestamp), backup->start_time);
fprintf(out, "START_TIME='%s'\n", timestamp);
if (backup->end_time > 0)
{
time2iso(timestamp, lengthof(timestamp), backup->end_time);
fprintf(out, "END_TIME='%s'\n", timestamp);
}
fprintf(out, "RECOVERY_XID=%u\n", backup->recovery_xid);
if (backup->recovery_time > 0)
{
time2iso(timestamp, lengthof(timestamp), backup->recovery_time);
fprintf(out, "RECOVERY_TIME='%s'\n", timestamp);
}
if (backup->data_bytes != BYTES_INVALID)
fprintf(out, "DATA_BYTES=" INT64_FORMAT "\n",
backup->data_bytes);
fprintf(out, "BLOCK_SIZE=%u\n", backup->block_size);
fprintf(out, "XLOG_BLOCK_SIZE=%u\n", backup->wal_block_size);
fprintf(out, "STATUS=%s\n", status2str(backup->status));
}
/* create backup.ini */
void
pgBackupWriteIni(pgBackup *backup)
{
FILE *fp = NULL;
char ini_path[MAXPGPATH];
pgBackupGetPath(backup, ini_path, lengthof(ini_path), BACKUP_INI_FILE);
fp = fopen(ini_path, "wt");
if (fp == NULL)
elog(ERROR_SYSTEM, "cannot open INI file \"%s\": %s", ini_path,
strerror(errno));
/* configuration section */
pgBackupWriteConfigSection(fp, backup);
/* result section */
pgBackupWriteResultSection(fp, backup);
fclose(fp);
}
/*
* Read backup.ini and create pgBackup.
* - Comment starts with ';'.
* - Do not care section.
*/
static pgBackup *
catalog_read_ini(const char *path)
{
pgBackup *backup;
char *backup_mode = NULL;
char *start_lsn = NULL;
char *stop_lsn = NULL;
char *status = NULL;
int i;
pgut_option options[] =
{
{ 's', 0, "backup-mode" , NULL, SOURCE_ENV },
{ 'u', 0, "timelineid" , NULL, SOURCE_ENV },
{ 's', 0, "start-lsn" , NULL, SOURCE_ENV },
{ 's', 0, "stop-lsn" , NULL, SOURCE_ENV },
{ 't', 0, "start-time" , NULL, SOURCE_ENV },
{ 't', 0, "end-time" , NULL, SOURCE_ENV },
{ 'u', 0, "recovery-xid" , NULL, SOURCE_ENV },
{ 't', 0, "recovery-time" , NULL, SOURCE_ENV },
{ 'I', 0, "data-bytes" , NULL, SOURCE_ENV },
{ 'u', 0, "block-size" , NULL, SOURCE_ENV },
{ 'u', 0, "xlog-block-size" , NULL, SOURCE_ENV },
{ 's', 0, "status" , NULL, SOURCE_ENV },
{ 0 }
};
if (access(path, F_OK) != 0)
return NULL;
backup = pgut_new(pgBackup);
catalog_init_config(backup);
i = 0;
options[i++].var = &backup_mode;
options[i++].var = &backup->tli;
options[i++].var = &start_lsn;
options[i++].var = &stop_lsn;
options[i++].var = &backup->start_time;
options[i++].var = &backup->end_time;
options[i++].var = &backup->recovery_xid;
options[i++].var = &backup->recovery_time;
options[i++].var = &backup->data_bytes;
options[i++].var = &backup->block_size;
options[i++].var = &backup->wal_block_size;
options[i++].var = &status;
Assert(i == lengthof(options) - 1);
pgut_readopt(path, options, ERROR_CORRUPTED);
if (backup_mode)
{
backup->backup_mode = parse_backup_mode(backup_mode);
free(backup_mode);
}
if (start_lsn)
{
uint32 xlogid;
uint32 xrecoff;
if (sscanf(start_lsn, "%X/%X", &xlogid, &xrecoff) == 2)
backup->start_lsn = (XLogRecPtr) ((uint64) xlogid << 32) | xrecoff;
else
elog(WARNING, "invalid START_LSN \"%s\"", start_lsn);
free(start_lsn);
}
if (stop_lsn)
{
uint32 xlogid;
uint32 xrecoff;
if (sscanf(stop_lsn, "%X/%X", &xlogid, &xrecoff) == 2)
backup->stop_lsn = (XLogRecPtr) ((uint64) xlogid << 32) | xrecoff;
else
elog(WARNING, "invalid STOP_LSN \"%s\"", stop_lsn);
free(stop_lsn);
}
if (status)
{
if (strcmp(status, "OK") == 0)
backup->status = BACKUP_STATUS_OK;
else if (strcmp(status, "RUNNING") == 0)
backup->status = BACKUP_STATUS_RUNNING;
else if (strcmp(status, "ERROR") == 0)
backup->status = BACKUP_STATUS_ERROR;
else if (strcmp(status, "DELETING") == 0)
backup->status = BACKUP_STATUS_DELETING;
else if (strcmp(status, "DELETED") == 0)
backup->status = BACKUP_STATUS_DELETED;
else if (strcmp(status, "DONE") == 0)
backup->status = BACKUP_STATUS_DONE;
else if (strcmp(status, "CORRUPT") == 0)
backup->status = BACKUP_STATUS_CORRUPT;
else
elog(WARNING, "invalid STATUS \"%s\"", status);
free(status);
}
return backup;
}
BackupMode
parse_backup_mode(const char *value)
{
const char *v = value;
size_t len;
/* Skip all spaces detected */
while (IsSpace(*v))
v++;
len = strlen(v);
if (len > 0 && pg_strncasecmp("full", v, strlen("full")) == 0)
return BACKUP_MODE_FULL;
else if (len > 0 && pg_strncasecmp("page", v, strlen("page")) == 0)
return BACKUP_MODE_DIFF_PAGE;
/* Backup mode is invalid, so leave with an error */
elog(ERROR_ARGS, "invalid backup-mode \"%s\"", value);
return BACKUP_MODE_INVALID;
}
/* free pgBackup object */
void
pgBackupFree(void *backup)
{
free(backup);
}
/* Compare two pgBackup with their IDs (start time) in ascending order */
int
pgBackupCompareId(const void *l, const void *r)
{
pgBackup *lp = *(pgBackup **)l;
pgBackup *rp = *(pgBackup **)r;
if (lp->start_time > rp->start_time)
return 1;
else if (lp->start_time < rp->start_time)
return -1;
else
return 0;
}
/* Compare two pgBackup with their IDs in descending order */
int
pgBackupCompareIdDesc(const void *l, const void *r)
{
return -pgBackupCompareId(l, r);
}
/*
* Construct absolute path of the backup directory.
* If subdir is not NULL, it will be appended after the path.
*/
void
pgBackupGetPath(const pgBackup *backup, char *path, size_t len, const char *subdir)
{
char datetime[20];
struct tm *tm;
/* generate $BACKUP_PATH/date/time path */
tm = localtime(&backup->start_time);
strftime(datetime, lengthof(datetime), "%Y%m%d/%H%M%S", tm);
if (subdir)
snprintf(path, len, "%s/%s/%s", backup_path, datetime, subdir);
else
snprintf(path, len, "%s/%s", backup_path, datetime);
}
void
catalog_init_config(pgBackup *backup)
{
backup->backup_mode = BACKUP_MODE_INVALID;
backup->status = BACKUP_STATUS_INVALID;
backup->tli = 0;
backup->start_lsn = 0;
backup->stop_lsn = 0;
backup->start_time = (time_t) 0;
backup->end_time = (time_t) 0;
backup->recovery_xid = 0;
backup->recovery_time = (time_t) 0;
backup->data_bytes = BYTES_INVALID;
}