1
0
mirror of https://github.com/postgrespro/pg_probackup.git synced 2024-11-24 08:52:38 +02:00
pg_probackup/catalog.c
Michael Paquier c8ebcc9c41 Rename project to pg_arman
This is officially a fork!
2014-01-27 12:02:56 +09:00

564 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,
_("can't 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, _("can't 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, _("can't 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, _("can't 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, _("can't read date directory \"%s\": %s"),
date_ent->d_name, strerror(errno));
goto err_proc;
}
closedir(time_dir);
time_dir = NULL;
}
if (errno)
{
elog(WARNING, _("can't 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
* incremental backup on current timeline.
*/
if (backup->status == BACKUP_STATUS_OK &&
backup->tli == tli &&
(backup->backup_mode == BACKUP_MODE_INCREMENTAL ||
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[] = { "", "INCREMENTAL", "FULL"};
fprintf(out, "# configuration\n");
fprintf(out, "BACKUP_MODE=%s\n", modes[backup->backup_mode]);
fprintf(out, "COMPRESS_DATA=%s\n", BOOL_TO_STR(backup->compress_data));
}
/*
* 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, _("can't 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 },
{ 'b', 0, "compress-data" , 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->compress_data;
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, len) == 0)
return BACKUP_MODE_FULL;
else if (len > 0 && pg_strncasecmp("incremental", v, len) == 0)
return BACKUP_MODE_INCREMENTAL;
/* 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->compress_data = false;
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;
}