1
0
mirror of https://github.com/postgrespro/pg_probackup.git synced 2024-11-25 09:01:48 +02:00
pg_probackup/catalog.c
Michael Paquier 37c3be1168 Refactor write/read calculation size of backups
This commit simplifies the way backup sizes are saved internally by
reusing the same variable for incremental and full backup, which were
using separated and exclusively used variables, resulted in a couple
of bytes wasted all the time. This was also reflected by a useless
column in the output table of subcommand "show".
2014-01-10 04:11:27 +09:00

638 lines
16 KiB
C

/*-------------------------------------------------------------------------
*
* catalog.c: backup catalog opration
*
* Copyright (c) 2009-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.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_rman.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 & serverlog 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;
}
/*
* Find the last completed archived WAL backup from the backup list
* on current timeline.
*/
pgBackup *
catalog_get_last_arclog_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 archived WAL backup */
if (backup->status == BACKUP_STATUS_OK &&
backup->tli == tli)
return backup;
}
return NULL;
}
/*
* Find the last completed serverlog backup from the backup list
* on current timeline.
*/
pgBackup *
catalog_get_last_srvlog_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 serverlog backup */
if (backup->status == BACKUP_STATUS_OK &&
backup->with_serverlog &&
backup->tli == tli)
return backup;
}
return NULL;
}
/* create backup directory in $BACKUP_PATH */
int
pgBackupCreateDir(pgBackup *backup)
{
int i;
char path[MAXPGPATH];
char *subdirs[] = { DATABASE_DIR, ARCLOG_DIR, SRVLOG_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[] = { "", "ARCHIVE", "INCREMENTAL", "FULL"};
fprintf(out, "# configuration\n");
fprintf(out, "BACKUP_MODE=%s\n", modes[backup->backup_mode]);
fprintf(out, "WITH_SERVERLOG=%s\n", BOOL_TO_STR(backup->with_serverlog));
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);
if (backup->arclog_bytes != BYTES_INVALID)
fprintf(out, "ARCLOG_BYTES=" INT64_FORMAT "\n",
backup->arclog_bytes);
if (backup->srvlog_bytes != BYTES_INVALID)
fprintf(out, "SRVLOG_BYTES=" INT64_FORMAT "\n",
backup->srvlog_bytes);
if (backup->backup_bytes != BYTES_INVALID)
fprintf(out, "BACKUP_BYTES=" INT64_FORMAT "\n",
backup->backup_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, "with-serverlog" , 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 },
{ 'I', 0, "arclog-bytes" , NULL, SOURCE_ENV },
{ 'I', 0, "srvlog-bytes" , NULL, SOURCE_ENV },
{ 'I', 0, "backup-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->with_serverlog;
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->arclog_bytes;
options[i++].var = &backup->srvlog_bytes;
options[i++].var = &backup->backup_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;
else if (len > 0 && pg_strncasecmp("archive", v, len) == 0)
return BACKUP_MODE_ARCHIVE;
/* 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->with_serverlog = false;
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;
backup->arclog_bytes = BYTES_INVALID;
backup->srvlog_bytes = BYTES_INVALID;
backup->backup_bytes = BYTES_INVALID;
}