mirror of
https://github.com/postgrespro/pg_probackup.git
synced 2025-04-12 11:58:15 +02:00
First import.
git-svn-id: http://pg-rman.googlecode.com/svn/trunk@2 182aca00-e38e-11de-a668-6fd11605f5ce
This commit is contained in:
parent
007464ae8f
commit
7fbb857d03
26
COPYRIGHT
Normal file
26
COPYRIGHT
Normal file
@ -0,0 +1,26 @@
|
||||
Copyright (c) 2008-2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
(NTT) nor the names of its contributors may be used to endorse or
|
||||
promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
40
Makefile
Normal file
40
Makefile
Normal file
@ -0,0 +1,40 @@
|
||||
PROGRAM = pg_rman
|
||||
SRCS = \
|
||||
backup.c \
|
||||
catalog.c \
|
||||
data.c \
|
||||
delete.c \
|
||||
dir.c \
|
||||
init.c \
|
||||
parray.c \
|
||||
pg_rman.c \
|
||||
restore.c \
|
||||
show.c \
|
||||
util.c \
|
||||
validate.c \
|
||||
xlog.c \
|
||||
pgsql_src/pg_ctl.c \
|
||||
pgsql_src/pg_crc.c \
|
||||
pgut/pgut.c \
|
||||
pgut/pgut-port.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
# pg_crc.c and are copied from PostgreSQL source tree.
|
||||
|
||||
# XXX for debug, add -g and disable optimization
|
||||
PG_CPPFLAGS = -I$(libpq_srcdir)
|
||||
PG_LIBS = $(libpq_pgport)
|
||||
|
||||
REGRESS = option init show_validate backup_restore
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
subdir = contrib/pg_rman
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
||||
|
||||
$(OBJS): pg_rman.h
|
593
catalog.c
Normal file
593
catalog.c
Normal file
@ -0,0 +1,593 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* catalog.c: backup catalog opration
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <libgen.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.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];
|
||||
|
||||
snprintf(id_path, lengthof(id_path), "%s/%s", 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)
|
||||
return (dir->dd_dta.attrib & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
#else
|
||||
char path[MAXPGPATH];
|
||||
DWORD attr;
|
||||
|
||||
/* dirent.d_type does not exists */
|
||||
strlcpy(path, dirpath, MAXPGPATH);
|
||||
strlcat(path, "/", MAXPGPATH);
|
||||
strlcat(path, ent->d_name, MAXPGPATH);
|
||||
attr = GetFileAttributes(path);
|
||||
return attr != (DWORD) -1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
#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 */
|
||||
snprintf(date_path, MAXPGPATH, "%s/%s", 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-directry 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)
|
||||
{
|
||||
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 */
|
||||
if (backup->status == BACKUP_STATUS_OK && HAVE_DATABASE(backup))
|
||||
return backup;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the last completed archived WAL backup from the backup list.
|
||||
*/
|
||||
pgBackup *
|
||||
catalog_get_last_arclog_backup(parray *backup_list)
|
||||
{
|
||||
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 && HAVE_ARCLOG(backup))
|
||||
return backup;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the last completed serverlog backup from the backup list.
|
||||
*/
|
||||
pgBackup *
|
||||
catalog_get_last_srvlog_backup(parray *backup_list)
|
||||
{
|
||||
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)
|
||||
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", backup->start_lsn.xlogid,
|
||||
backup->start_lsn.xrecoff);
|
||||
fprintf(out, "STOP_LSN=%x/%08x\n", backup->stop_lsn.xlogid,
|
||||
backup->stop_lsn.xrecoff);
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
fprintf(out, "START_TIME='%s'\n", timestamp);
|
||||
time2iso(timestamp, lengthof(timestamp), backup->end_time);
|
||||
fprintf(out, "END_TIME='%s'\n", timestamp);
|
||||
fprintf(out, "TOTAL_DATA_BYTES=" INT64_FORMAT "\n", backup->total_data_bytes);
|
||||
fprintf(out, "READ_DATA_BYTES=" INT64_FORMAT "\n", backup->read_data_bytes);
|
||||
fprintf(out, "READ_ARCLOG_BYTES=" INT64_FORMAT "\n", backup->read_arclog_bytes);
|
||||
fprintf(out, "READ_SRVLOG_BYTES=" INT64_FORMAT "\n", backup->read_srvlog_bytes);
|
||||
fprintf(out, "WRITE_BYTES=" INT64_FORMAT "\n", backup->write_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 },
|
||||
{ 'I', 0, "total-data-bytes" , NULL, SOURCE_ENV },
|
||||
{ 'I', 0, "read-data-bytes" , NULL, SOURCE_ENV },
|
||||
{ 'I', 0, "read-arclog-bytes" , NULL, SOURCE_ENV },
|
||||
{ 'I', 0, "read-srvlog-bytes" , NULL, SOURCE_ENV },
|
||||
{ 'I', 0, "write-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 }
|
||||
};
|
||||
|
||||
backup = (pgBackup *) pgut_malloc(sizeof(*backup));
|
||||
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->total_data_bytes;
|
||||
options[i++].var = &backup->read_data_bytes;
|
||||
options[i++].var = &backup->read_arclog_bytes;
|
||||
options[i++].var = &backup->read_srvlog_bytes;
|
||||
options[i++].var = &backup->write_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, WARNING);
|
||||
free(backup_mode);
|
||||
}
|
||||
|
||||
if (start_lsn)
|
||||
{
|
||||
XLogRecPtr lsn;
|
||||
if (sscanf(start_lsn, "%X/%X", &lsn.xlogid, &lsn.xrecoff) == 2)
|
||||
backup->start_lsn = lsn;
|
||||
else
|
||||
elog(WARNING, _("invalid START_LSN \"%s\""), start_lsn);
|
||||
free(start_lsn);
|
||||
}
|
||||
|
||||
if (stop_lsn)
|
||||
{
|
||||
XLogRecPtr lsn;
|
||||
if (sscanf(stop_lsn, "%X/%X", &lsn.xlogid, &lsn.xrecoff) == 2)
|
||||
backup->stop_lsn = lsn;
|
||||
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, int elevel)
|
||||
{
|
||||
const char *v = value;
|
||||
size_t len;
|
||||
|
||||
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;
|
||||
|
||||
elog(elevel, _("invalid backup-mode \"%s\""), value);
|
||||
return BACKUP_MODE_INVALID;
|
||||
}
|
||||
|
||||
/* free pgBackup object */
|
||||
void
|
||||
pgBackupFree(void *backup)
|
||||
{
|
||||
free(backup);
|
||||
}
|
||||
|
||||
/* Compare two pgBackup with their ID (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 ID 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.xlogid = 0;
|
||||
backup->start_lsn.xrecoff = 0;
|
||||
backup->stop_lsn.xlogid = 0;
|
||||
backup->stop_lsn.xrecoff = 0;
|
||||
backup->start_time = (time_t) 0;
|
||||
backup->end_time = (time_t) 0;
|
||||
backup->total_data_bytes = BYTES_INVALID;
|
||||
backup->read_data_bytes = BYTES_INVALID;
|
||||
backup->read_arclog_bytes = BYTES_INVALID;
|
||||
backup->read_srvlog_bytes = BYTES_INVALID;
|
||||
backup->write_bytes = BYTES_INVALID;
|
||||
}
|
886
data.c
Normal file
886
data.c
Normal file
@ -0,0 +1,886 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* data.c: compress / uncompress data pages
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "libpq/pqsignal.h"
|
||||
#include "storage/block.h"
|
||||
#include "storage/bufpage.h"
|
||||
|
||||
#if PG_VERSION_NUM < 80300
|
||||
#define XLogRecPtrIsInvalid(r) ((r).xrecoff == 0)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
#include <zlib.h>
|
||||
|
||||
#define zlibOutSize 4096
|
||||
#define zlibInSize 4096
|
||||
|
||||
static int doDeflate(z_stream *zp, size_t in_size, size_t out_size, void *inbuf,
|
||||
void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *write_size,
|
||||
int flash);
|
||||
static int doInflate(z_stream *zp, size_t in_size, size_t out_size,void *inbuf,
|
||||
void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *read_size);
|
||||
|
||||
static int
|
||||
doDeflate(z_stream *zp, size_t in_size, size_t out_size, void *inbuf,
|
||||
void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *write_size,
|
||||
int flash)
|
||||
{
|
||||
int status;
|
||||
|
||||
zp->next_in = inbuf;
|
||||
zp->avail_in = in_size;
|
||||
|
||||
/* compresses until an input buffer becomes empty. */
|
||||
do
|
||||
{
|
||||
if (interrupted)
|
||||
elog(ERROR_INTERRUPTED, _("interrupted during deflate"));
|
||||
|
||||
status = deflate(zp, flash);
|
||||
|
||||
if (status == Z_STREAM_ERROR)
|
||||
{
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't compress data: %s"), zp->msg);
|
||||
}
|
||||
|
||||
if (fwrite(outbuf, 1, out_size - zp->avail_out, out) !=
|
||||
out_size - zp->avail_out)
|
||||
{
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't write file: %s"), strerror(errno));
|
||||
}
|
||||
|
||||
/* update CRC */
|
||||
COMP_CRC32(*crc, outbuf, out_size - zp->avail_out);
|
||||
|
||||
*write_size += out_size - zp->avail_out;
|
||||
|
||||
zp->next_out = outbuf;
|
||||
zp->avail_out = out_size;
|
||||
} while (zp->avail_in != 0);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int
|
||||
doInflate(z_stream *zp, size_t in_size, size_t out_size,void *inbuf,
|
||||
void *outbuf, FILE *in, FILE *out, pg_crc32 *crc, size_t *read_size)
|
||||
{
|
||||
int status = Z_OK;
|
||||
|
||||
zp->next_out = outbuf;
|
||||
zp->avail_out = out_size;
|
||||
|
||||
/* decompresses until an output buffer becomes full. */
|
||||
for (;;)
|
||||
{
|
||||
if (interrupted)
|
||||
elog(ERROR_INTERRUPTED, _("interrupted during inflate"));
|
||||
|
||||
/* input buffer becomes empty, read it from a file. */
|
||||
if (zp->avail_in == 0)
|
||||
{
|
||||
size_t read_len;
|
||||
|
||||
read_len = fread(inbuf, 1, in_size, in);
|
||||
|
||||
if (read_len != in_size)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
|
||||
if (!feof(in))
|
||||
{
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("can't read compress file: %s"), strerror(errno_tmp));
|
||||
}
|
||||
|
||||
if (read_len == 0 && *read_size == 0)
|
||||
return Z_STREAM_END;
|
||||
}
|
||||
|
||||
zp->next_in = inbuf;
|
||||
zp->avail_in = read_len;
|
||||
*read_size += read_len;
|
||||
}
|
||||
|
||||
/* decompresses input file data */
|
||||
status = inflate(zp, Z_NO_FLUSH);
|
||||
|
||||
if (status == Z_STREAM_END)
|
||||
{
|
||||
if (feof(in))
|
||||
break;
|
||||
/* not reached to EOF, read again */
|
||||
}
|
||||
else if (status == Z_OK)
|
||||
{
|
||||
if (zp->avail_out == 0)
|
||||
break;
|
||||
/* more input needed to fill out_buf */
|
||||
}
|
||||
else if (status != Z_OK)
|
||||
{
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't uncompress data: %s"), strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* update CRC */
|
||||
COMP_CRC32(*crc, outbuf, out_size - zp->avail_out);
|
||||
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef union DataPage
|
||||
{
|
||||
PageHeaderData header;
|
||||
char data[BLCKSZ];
|
||||
} DataPage;
|
||||
|
||||
typedef struct BackupPageHeader
|
||||
{
|
||||
BlockNumber block; /* block number */
|
||||
uint16 hole_offset; /* number of bytes before "hole" */
|
||||
uint16 hole_length; /* number of bytes in "hole" */
|
||||
} BackupPageHeader;
|
||||
|
||||
static bool
|
||||
is_valid_header(const PageHeader page)
|
||||
{
|
||||
const char *pagebytes;
|
||||
int i;
|
||||
|
||||
/* Check normal case */
|
||||
if (PageGetPageSize(page) == BLCKSZ &&
|
||||
PageGetPageLayoutVersion(page) == PG_PAGE_LAYOUT_VERSION &&
|
||||
#if PG_VERSION_NUM >= 80300
|
||||
(page->pd_flags & ~PD_VALID_FLAG_BITS) == 0 &&
|
||||
#endif
|
||||
page->pd_lower >= SizeOfPageHeaderData &&
|
||||
page->pd_lower <= page->pd_upper &&
|
||||
page->pd_upper <= page->pd_special &&
|
||||
page->pd_special <= BLCKSZ &&
|
||||
page->pd_special == MAXALIGN(page->pd_special))
|
||||
return true;
|
||||
|
||||
/* Check all-zeroes case */
|
||||
pagebytes = (char *) page;
|
||||
for (i = 0; i < BLCKSZ; i++)
|
||||
{
|
||||
if (pagebytes[i] != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Backup data file in the from_root directory to the to_root directory with
|
||||
* same relative path.
|
||||
* If lsn is not NULL, pages only which are modified after the lsn will be
|
||||
* copied.
|
||||
*/
|
||||
void
|
||||
backup_data_file(const char *from_root, const char *to_root,
|
||||
pgFile *file, const XLogRecPtr *lsn, bool compress_data)
|
||||
{
|
||||
char to_path[MAXPGPATH];
|
||||
FILE *in;
|
||||
FILE *out;
|
||||
BackupPageHeader header;
|
||||
DataPage page; /* used as read buffer */
|
||||
BlockNumber blknum;
|
||||
size_t read_len;
|
||||
int errno_tmp;
|
||||
pg_crc32 crc;
|
||||
#ifdef HAVE_LIBZ
|
||||
z_stream z;
|
||||
char outbuf[zlibOutSize];
|
||||
#endif
|
||||
|
||||
/* open backup mode file for read */
|
||||
in = fopen(file->path, "r");
|
||||
if (in == NULL)
|
||||
{
|
||||
/* meybe vanished, it's not error */
|
||||
if (errno == ENOENT)
|
||||
return;
|
||||
|
||||
elog(ERROR_SYSTEM, _("can't open backup mode file \"%s\": %s"),
|
||||
file->path, strerror(errno));
|
||||
}
|
||||
|
||||
/* open backup file for write */
|
||||
if (check)
|
||||
snprintf(to_path, lengthof(to_path), "%s/tmp", backup_path);
|
||||
else
|
||||
snprintf(to_path, lengthof(to_path), "%s/%s",
|
||||
to_root, file->path + strlen(from_root) + 1);
|
||||
out = fopen(to_path, "w");
|
||||
if (out == NULL)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
fclose(in);
|
||||
elog(ERROR_SYSTEM, _("can't open backup file \"%s\": %s"),
|
||||
to_path, strerror(errno_tmp));
|
||||
}
|
||||
|
||||
INIT_CRC32(crc);
|
||||
|
||||
/* reset size summary */
|
||||
file->read_size = 0;
|
||||
file->write_size = 0;
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
z.zalloc = Z_NULL;
|
||||
z.zfree = Z_NULL;
|
||||
z.opaque = Z_NULL;
|
||||
|
||||
if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK)
|
||||
elog(ERROR_SYSTEM, _("can't initialize compression library: %s"),
|
||||
z.msg);
|
||||
|
||||
z.avail_in = 0;
|
||||
z.next_out = (void *) outbuf;
|
||||
z.avail_out = zlibOutSize;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* read each page and write the page excluding hole */
|
||||
for (blknum = 0;
|
||||
(read_len = fread(&page, 1, sizeof(page), in)) == sizeof(page);
|
||||
++blknum)
|
||||
{
|
||||
int upper_offset;
|
||||
int upper_length;
|
||||
|
||||
header.block = blknum;
|
||||
|
||||
/*
|
||||
* If a invalid data page was found, fallback to simple copy to ensure
|
||||
* all pages in the file don't have BackupPageHeader.
|
||||
*/
|
||||
if (!is_valid_header(&page.header) ||
|
||||
!XLogRecPtrIsInvalid(PageGetLSN(&page.header)))
|
||||
{
|
||||
elog(LOG, "%s fall back to simple copy", file->path);
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
file->is_datafile = false;
|
||||
copy_file(from_root, to_root, file,
|
||||
compress_data ? COMPRESSION : NO_COMPRESSION);
|
||||
return;
|
||||
}
|
||||
|
||||
file->read_size += read_len;
|
||||
|
||||
/* if the page has not been modified since last backup, skip it */
|
||||
if (lsn && !XLogRecPtrIsInvalid(PageGetLSN(&page.header)) &&
|
||||
XLByteLT(PageGetLSN(&page.header), *lsn))
|
||||
continue;
|
||||
|
||||
header.hole_offset = page.header.pd_lower;
|
||||
header.hole_length = page.header.pd_upper - page.header.pd_lower;
|
||||
|
||||
upper_offset = header.hole_offset + header.hole_length;
|
||||
upper_length = BLCKSZ - upper_offset;
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
doDeflate(&z, sizeof(header), sizeof(outbuf), &header, outbuf, in,
|
||||
out, &crc, &file->write_size, Z_NO_FLUSH);
|
||||
doDeflate(&z, header.hole_offset, sizeof(outbuf), page.data, outbuf,
|
||||
in, out, &crc, &file->write_size, Z_NO_FLUSH);
|
||||
doDeflate(&z, upper_length, sizeof(outbuf),
|
||||
page.data + upper_offset, outbuf, in, out, &crc,
|
||||
&file->write_size, Z_NO_FLUSH);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* write data page excluding hole */
|
||||
if (fwrite(&header, 1, sizeof(header), out) != sizeof(header) ||
|
||||
fwrite(page.data, 1, header.hole_offset, out) != header.hole_offset ||
|
||||
fwrite(page.data + upper_offset, 1, upper_length, out) != upper_length)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
/* oops */
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't write at block %u of \"%s\": %s"),
|
||||
blknum, to_path, strerror(errno_tmp));
|
||||
}
|
||||
|
||||
/* update CRC */
|
||||
COMP_CRC32(crc, &header, sizeof(header));
|
||||
COMP_CRC32(crc, page.data, header.hole_offset);
|
||||
COMP_CRC32(crc, page.data + upper_offset, upper_length);
|
||||
|
||||
file->write_size += sizeof(header) + read_len - header.hole_length;
|
||||
}
|
||||
|
||||
}
|
||||
errno_tmp = errno;
|
||||
if (!feof(in))
|
||||
{
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't read backup mode file \"%s\": %s"),
|
||||
file->path, strerror(errno_tmp));
|
||||
}
|
||||
|
||||
/*
|
||||
* The odd size page at the tail is probably a page exactly written now, so
|
||||
* write whole of it.
|
||||
*/
|
||||
if (read_len > 0)
|
||||
{
|
||||
/*
|
||||
* If the odd size page is the 1st page, fallback to simple copy because
|
||||
* the file is not a datafile.
|
||||
* Otherwise treat the page as a datapage with no hole.
|
||||
*/
|
||||
if (blknum == 0)
|
||||
file->is_datafile = false;
|
||||
else
|
||||
{
|
||||
header.block = blknum;
|
||||
header.hole_offset = 0;
|
||||
header.hole_length = 0;
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
doDeflate(&z, sizeof(header), sizeof(outbuf), &header, outbuf,
|
||||
in, out, &crc, &file->write_size, Z_NO_FLUSH);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (fwrite(&header, 1, sizeof(header), out) != sizeof(header))
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
/* oops */
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM,
|
||||
_("can't write at block %u of \"%s\": %s"),
|
||||
blknum, to_path, strerror(errno_tmp));
|
||||
}
|
||||
COMP_CRC32(crc, &header, sizeof(header));
|
||||
file->write_size += sizeof(header);
|
||||
}
|
||||
}
|
||||
|
||||
/* write odd size page image */
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
doDeflate(&z, read_len, sizeof(outbuf), page.data, outbuf, in, out,
|
||||
&crc, &file->write_size, Z_NO_FLUSH);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (fwrite(page.data, 1, read_len, out) != read_len)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
/* oops */
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't write at block %u of \"%s\": %s"),
|
||||
blknum, to_path, strerror(errno_tmp));
|
||||
}
|
||||
|
||||
COMP_CRC32(crc, page.data, read_len);
|
||||
file->write_size += read_len;
|
||||
}
|
||||
|
||||
file->read_size += read_len;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
if (file->read_size > 0)
|
||||
{
|
||||
while (doDeflate(&z, 0, sizeof(outbuf), NULL, outbuf, in, out, &crc,
|
||||
&file->write_size, Z_FINISH) != Z_STREAM_END)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (deflateEnd(&z) != Z_OK)
|
||||
{
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
/* update file permission */
|
||||
if (!check && chmod(to_path, FILE_PERMISSION) == -1)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't change mode of \"%s\": %s"), file->path,
|
||||
strerror(errno_tmp));
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
/* finish CRC calculation and store into pgFile */
|
||||
FIN_CRC32(crc);
|
||||
file->crc = crc;
|
||||
|
||||
/* Treat empty file as not-datafile */
|
||||
if (file->read_size == 0)
|
||||
file->is_datafile = false;
|
||||
|
||||
/* We do not backup if all pages skipped. */
|
||||
if (file->write_size == 0 && file->read_size > 0)
|
||||
if (remove(to_path) == -1)
|
||||
elog(ERROR_SYSTEM, _("can't remove file \"%s\": %s"), to_path,
|
||||
strerror(errno));
|
||||
|
||||
/* remove $BACKUP_PATH/tmp created during check */
|
||||
if (check)
|
||||
remove(to_path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Restore files in the from_root directory to the to_root directory with
|
||||
* same relative path.
|
||||
*/
|
||||
void
|
||||
restore_data_file(const char *from_root, const char *to_root, pgFile *file, bool compress_data)
|
||||
{
|
||||
char to_path[MAXPGPATH];
|
||||
FILE *in;
|
||||
FILE *out;
|
||||
BackupPageHeader header;
|
||||
BlockNumber blknum;
|
||||
#ifdef HAVE_LIBZ
|
||||
z_stream z;
|
||||
int status;
|
||||
char inbuf[zlibInSize];
|
||||
pg_crc32 crc;
|
||||
size_t read_size;
|
||||
#endif
|
||||
|
||||
/* If the file is not a datafile, copy it. */
|
||||
if (!file->is_datafile)
|
||||
{
|
||||
copy_file(from_root, to_root, file,
|
||||
compress_data ? DECOMPRESSION : NO_COMPRESSION);
|
||||
return;
|
||||
}
|
||||
|
||||
/* open backup mode file for read */
|
||||
in = fopen(file->path, "r");
|
||||
if (in == NULL)
|
||||
{
|
||||
elog(ERROR_SYSTEM, _("can't open backup file \"%s\": %s"), file->path,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
/* open backup file for write */
|
||||
snprintf(to_path, lengthof(to_path), "%s/%s", to_root,
|
||||
file->path + strlen(from_root) + 1);
|
||||
out = fopen(to_path, "w");
|
||||
if (out == NULL)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
fclose(in);
|
||||
elog(ERROR_SYSTEM, _("can't open restore target file \"%s\": %s"),
|
||||
to_path, strerror(errno_tmp));
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
z.zalloc = Z_NULL;
|
||||
z.zfree = Z_NULL;
|
||||
z.opaque = Z_NULL;
|
||||
z.next_in = Z_NULL;
|
||||
z.avail_in = 0;
|
||||
|
||||
if (inflateInit(&z) != Z_OK)
|
||||
elog(ERROR_SYSTEM, _("can't initialize compression library: %s"),
|
||||
z.msg);
|
||||
INIT_CRC32(crc);
|
||||
read_size = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (blknum = 0; ;blknum++)
|
||||
{
|
||||
size_t read_len;
|
||||
DataPage page; /* used as read buffer */
|
||||
int upper_offset;
|
||||
int upper_length;
|
||||
|
||||
/* read BackupPageHeader */
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
status = doInflate(&z, sizeof(inbuf), sizeof(header), inbuf,
|
||||
&header, in, out, &crc, &read_size);
|
||||
if (status == Z_STREAM_END)
|
||||
{
|
||||
if (z.avail_out != sizeof(header))
|
||||
elog(ERROR_CORRUPTED, _("backup is broken header"));
|
||||
break;
|
||||
}
|
||||
if (z.avail_out != 0)
|
||||
elog(ERROR_SYSTEM, _("can't read block %u of \"%s\""),
|
||||
blknum, file->path);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
read_len = fread(&header, 1, sizeof(header), in);
|
||||
if (read_len != sizeof(header))
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
if (read_len == 0 && feof(in))
|
||||
break; /* EOF found */
|
||||
else if (read_len != 0 && feof(in))
|
||||
{
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("odd size page found at block %u of \"%s\""),
|
||||
blknum, file->path);
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR_SYSTEM, _("can't read block %u of \"%s\": %s"),
|
||||
blknum, file->path, strerror(errno_tmp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (header.block < blknum || header.hole_offset > BLCKSZ ||
|
||||
(int) header.hole_offset + (int) header.hole_length > BLCKSZ)
|
||||
{
|
||||
elog(ERROR_CORRUPTED, _("backup is broken at block %u"),
|
||||
blknum);
|
||||
}
|
||||
|
||||
upper_offset = header.hole_offset + header.hole_length;
|
||||
upper_length = BLCKSZ - upper_offset;
|
||||
|
||||
/* read lower/upper into page.data and restore hole */
|
||||
memset(page.data + header.hole_offset, 0, header.hole_length);
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data)
|
||||
{
|
||||
elog(LOG, "\n%s() %s %d %d", __FUNCTION__, file->path, header.hole_offset, upper_length);
|
||||
if (header.hole_offset > 0)
|
||||
{
|
||||
doInflate(&z, sizeof(inbuf), header.hole_offset, inbuf, page.data,
|
||||
in, out, &crc, &read_size);
|
||||
if (z.avail_out != 0)
|
||||
elog(ERROR_SYSTEM, _("can't read block %u of \"%s\""),
|
||||
blknum, file->path);
|
||||
}
|
||||
|
||||
if (upper_length > 0)
|
||||
{
|
||||
doInflate(&z, sizeof(inbuf), upper_length, inbuf,
|
||||
page.data + upper_offset, in, out, &crc, &read_size);
|
||||
if (z.avail_out != 0)
|
||||
elog(ERROR_SYSTEM, _("can't read block %u of \"%s\""),
|
||||
blknum, file->path);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (fread(page.data, 1, header.hole_offset, in) != header.hole_offset ||
|
||||
fread(page.data + upper_offset, 1, upper_length, in) != upper_length)
|
||||
{
|
||||
elog(ERROR_SYSTEM, _("can't read block %u of \"%s\": %s"),
|
||||
blknum, file->path, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* by the incremental backup, we skip in a page without the update. */
|
||||
if (blknum != header.block)
|
||||
{
|
||||
if (fseek(out, (header.block - blknum) * BLCKSZ, SEEK_CUR) < 0)
|
||||
elog(ERROR_SYSTEM, _("can't seek restore target file \"%s\": %s"),
|
||||
to_path, strerror(errno));
|
||||
blknum = header.block;
|
||||
}
|
||||
|
||||
if (fwrite(page.data, 1, sizeof(page), out) != sizeof(page))
|
||||
{
|
||||
elog(ERROR_SYSTEM, _("can't write block %u of \"%s\": %s"),
|
||||
blknum, file->path, strerror(errno));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
if (compress_data && inflateEnd(&z) != Z_OK)
|
||||
elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg);
|
||||
#endif
|
||||
|
||||
/* update file permission */
|
||||
if (chmod(to_path, file->mode) == -1)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't change mode of \"%s\": %s"), to_path,
|
||||
strerror(errno_tmp));
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
void
|
||||
copy_file(const char *from_root, const char *to_root, pgFile *file,
|
||||
CompressionMode mode)
|
||||
{
|
||||
char to_path[MAXPGPATH];
|
||||
FILE *in;
|
||||
FILE *out;
|
||||
size_t read_len = 0;
|
||||
int errno_tmp;
|
||||
char buf[8192];
|
||||
struct stat st;
|
||||
pg_crc32 crc;
|
||||
#ifdef HAVE_LIBZ
|
||||
z_stream z;
|
||||
int status;
|
||||
char outbuf[zlibOutSize];
|
||||
char inbuf[zlibInSize];
|
||||
#endif
|
||||
|
||||
/* open backup mode file for read */
|
||||
in = fopen(file->path, "r");
|
||||
if (in == NULL)
|
||||
{
|
||||
/* meybe deleted, it's not error */
|
||||
if (errno == ENOENT)
|
||||
return;
|
||||
|
||||
elog(ERROR_SYSTEM, _("can't open source file \"%s\": %s"), file->path,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
/* open backup file for write */
|
||||
if (check)
|
||||
snprintf(to_path, lengthof(to_path), "%s/tmp", backup_path);
|
||||
else
|
||||
snprintf(to_path, lengthof(to_path), "%s/%s", to_root,
|
||||
file->path + strlen(from_root) + 1);
|
||||
out = fopen(to_path, "w");
|
||||
if (out == NULL)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
fclose(in);
|
||||
elog(ERROR_SYSTEM, _("can't open destination file \"%s\": %s"),
|
||||
to_path, strerror(errno_tmp));
|
||||
}
|
||||
|
||||
/* stat source file to change mode of destination file */
|
||||
if (fstat(fileno(in), &st) == -1)
|
||||
{
|
||||
elog(ERROR_SYSTEM, _("can't stat \"%s\": %s"), file->path,
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
INIT_CRC32(crc);
|
||||
|
||||
/* reset size summary */
|
||||
file->read_size = 0;
|
||||
file->write_size = 0;
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
z.zalloc = Z_NULL;
|
||||
z.zfree = Z_NULL;
|
||||
z.opaque = Z_NULL;
|
||||
|
||||
if (mode == COMPRESSION)
|
||||
{
|
||||
if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK)
|
||||
elog(ERROR_SYSTEM, _("can't initialize compression library: %s"),
|
||||
z.msg);
|
||||
|
||||
z.avail_in = 0;
|
||||
z.next_out = (void *) outbuf;
|
||||
z.avail_out = zlibOutSize;
|
||||
}
|
||||
else if (mode == DECOMPRESSION)
|
||||
{
|
||||
z.next_in = Z_NULL;
|
||||
z.avail_in = 0;
|
||||
if (inflateInit(&z) != Z_OK)
|
||||
elog(ERROR_SYSTEM, _("can't initialize compression library: %s"),
|
||||
z.msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* copy content and calc CRC */
|
||||
for (;;)
|
||||
{
|
||||
#ifdef HAVE_LIBZ
|
||||
if (mode == COMPRESSION)
|
||||
{
|
||||
if ((read_len = fread(buf, 1, sizeof(buf), in)) != sizeof(buf))
|
||||
break;
|
||||
|
||||
doDeflate(&z, read_len, sizeof(outbuf), buf, outbuf, in, out, &crc,
|
||||
&file->write_size, Z_NO_FLUSH);
|
||||
file->read_size += sizeof(buf);
|
||||
}
|
||||
else if (mode == DECOMPRESSION)
|
||||
{
|
||||
status = doInflate(&z, sizeof(inbuf), sizeof(outbuf), inbuf, outbuf,
|
||||
in, out, &crc, &file->read_size);
|
||||
if (fwrite(outbuf, 1, sizeof(outbuf) - z.avail_out, out) !=
|
||||
sizeof(outbuf) - z.avail_out)
|
||||
{
|
||||
errno_tmp = errno;
|
||||
/* oops */
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't write to \"%s\": %s"), to_path,
|
||||
strerror(errno_tmp));
|
||||
}
|
||||
|
||||
file->write_size += sizeof(outbuf) - z.avail_out;
|
||||
if (status == Z_STREAM_END)
|
||||
break;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if ((read_len = fread(buf, 1, sizeof(buf), in)) != sizeof(buf))
|
||||
break;
|
||||
|
||||
if (fwrite(buf, 1, read_len, out) != read_len)
|
||||
{
|
||||
errno_tmp = errno;
|
||||
/* oops */
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't write to \"%s\": %s"), to_path,
|
||||
strerror(errno_tmp));
|
||||
}
|
||||
/* update CRC */
|
||||
COMP_CRC32(crc, buf, read_len);
|
||||
|
||||
file->write_size += sizeof(buf);
|
||||
file->read_size += sizeof(buf);
|
||||
}
|
||||
}
|
||||
errno_tmp = errno;
|
||||
if (!feof(in))
|
||||
{
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't read backup mode file \"%s\": %s"),
|
||||
file->path, strerror(errno_tmp));
|
||||
}
|
||||
|
||||
/* copy odd part. */
|
||||
if (read_len > 0)
|
||||
{
|
||||
#ifdef HAVE_LIBZ
|
||||
if (mode == COMPRESSION)
|
||||
{
|
||||
doDeflate(&z, read_len, sizeof(outbuf), buf, outbuf, in, out, &crc,
|
||||
&file->write_size, Z_NO_FLUSH);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (fwrite(buf, 1, read_len, out) != read_len)
|
||||
{
|
||||
errno_tmp = errno;
|
||||
/* oops */
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't write to \"%s\": %s"), to_path,
|
||||
strerror(errno_tmp));
|
||||
}
|
||||
/* update CRC */
|
||||
COMP_CRC32(crc, buf, read_len);
|
||||
|
||||
file->write_size += read_len;
|
||||
}
|
||||
|
||||
file->read_size += read_len;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
if (mode == COMPRESSION)
|
||||
{
|
||||
if (file->read_size > 0)
|
||||
{
|
||||
while (doDeflate(&z, 0, sizeof(outbuf), NULL, outbuf, in, out, &crc,
|
||||
&file->write_size, Z_FINISH) != Z_STREAM_END)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (deflateEnd(&z) != Z_OK)
|
||||
elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg);
|
||||
}
|
||||
else if (mode == DECOMPRESSION)
|
||||
{
|
||||
if (inflateEnd(&z) != Z_OK)
|
||||
elog(ERROR_SYSTEM, _("can't close compression stream: %s"), z.msg);
|
||||
}
|
||||
|
||||
#endif
|
||||
/* finish CRC calculation and store into pgFile */
|
||||
FIN_CRC32(crc);
|
||||
file->crc = crc;
|
||||
|
||||
/* update file permission */
|
||||
if (chmod(to_path, st.st_mode) == -1)
|
||||
{
|
||||
errno_tmp = errno;
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
elog(ERROR_SYSTEM, _("can't change mode of \"%s\": %s"), to_path,
|
||||
strerror(errno_tmp));
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
if (check)
|
||||
remove(to_path);
|
||||
}
|
18
data/sample_backup/20090531/170553/backup.ini
Normal file
18
data/sample_backup/20090531/170553/backup.ini
Normal file
@ -0,0 +1,18 @@
|
||||
# configuration
|
||||
BACKUP_MODE=FULL
|
||||
WITH_SERVERLOG=NO
|
||||
COMPRESS_DATA=NO
|
||||
# result
|
||||
TIMELINEID=1
|
||||
START_LSN=0/0b40c800
|
||||
STOP_LSN=0/0b4c8020
|
||||
START_TIME='2009-05-31 17:05:53'
|
||||
END_TIME='2009-05-31 17:09:13'
|
||||
TOTAL_DATA_BYTES=1242102558
|
||||
READ_DATA_BYTES=1024
|
||||
READ_ARCLOG_BYTES=9223372036854775807
|
||||
READ_SRVLOG_BYTES=-1
|
||||
WRITE_BYTES=242102558
|
||||
BLOCK_SIZE=8192
|
||||
XLOG_BLOCK_SIZE=8192
|
||||
STATUS=DONE
|
1
data/sample_backup/20090531/170553/database/PG_VERSION
Normal file
1
data/sample_backup/20090531/170553/database/PG_VERSION
Normal file
@ -0,0 +1 @@
|
||||
8.4
|
0
data/sample_backup/20090531/170553/file_arclog.txt
Normal file
0
data/sample_backup/20090531/170553/file_arclog.txt
Normal file
1
data/sample_backup/20090531/170553/file_database.txt
Normal file
1
data/sample_backup/20090531/170553/file_database.txt
Normal file
@ -0,0 +1 @@
|
||||
PG_VERSION f 4 4277607361 0600 2009-08-06 18:40:18
|
18
data/sample_backup/20090601/170553/backup.ini
Normal file
18
data/sample_backup/20090601/170553/backup.ini
Normal file
@ -0,0 +1,18 @@
|
||||
# configuration
|
||||
BACKUP_MODE=INCREMENTAL
|
||||
WITH_SERVERLOG=NO
|
||||
COMPRESS_DATA=NO
|
||||
# result
|
||||
TIMELINEID=1
|
||||
START_LSN=0/0b40c800
|
||||
STOP_LSN=0/0b4c8020
|
||||
START_TIME='2009-06-01 17:05:53'
|
||||
END_TIME='2009-06-01 17:09:13'
|
||||
TOTAL_DATA_BYTES=1242102558
|
||||
READ_DATA_BYTES=9223372036854775807
|
||||
READ_ARCLOG_BYTES=16777216
|
||||
READ_SRVLOG_BYTES=-1
|
||||
WRITE_BYTES=162372983
|
||||
BLOCK_SIZE=8192
|
||||
XLOG_BLOCK_SIZE=8192
|
||||
STATUS=DONE
|
1
data/sample_backup/20090601/170553/database/PG_VERSION
Normal file
1
data/sample_backup/20090601/170553/database/PG_VERSION
Normal file
@ -0,0 +1 @@
|
||||
8.4
|
0
data/sample_backup/20090601/170553/file_arclog.txt
Normal file
0
data/sample_backup/20090601/170553/file_arclog.txt
Normal file
1
data/sample_backup/20090601/170553/file_database.txt
Normal file
1
data/sample_backup/20090601/170553/file_database.txt
Normal file
@ -0,0 +1 @@
|
||||
PG_VERSION f 4 0 0600 2009-08-06 18:40:18
|
18
data/sample_backup/20090602/170553/backup.ini
Normal file
18
data/sample_backup/20090602/170553/backup.ini
Normal file
@ -0,0 +1,18 @@
|
||||
# configuration
|
||||
BACKUP_MODE=ARCHIVE
|
||||
WITH_SERVERLOG=YES
|
||||
COMPRESS_DATA=NO
|
||||
# result
|
||||
TIMELINEID=1
|
||||
START_LSN=0/0b40c800
|
||||
STOP_LSN=0/0b4c8020
|
||||
START_TIME='2009-06-02 17:05:03'
|
||||
END_TIME='2009-06-02 17:05:03'
|
||||
TOTAL_DATA_BYTES=-1
|
||||
READ_DATA_BYTES=-1
|
||||
READ_ARCLOG_BYTES=-1
|
||||
READ_SRVLOG_BYTES=4335423
|
||||
WRITE_BYTES=162372983
|
||||
BLOCK_SIZE=8192
|
||||
XLOG_BLOCK_SIZE=8192
|
||||
STATUS=DELETED
|
18
data/sample_backup/20090603/170553/backup.ini
Normal file
18
data/sample_backup/20090603/170553/backup.ini
Normal file
@ -0,0 +1,18 @@
|
||||
# configuration
|
||||
BACKUP_MODE=FULL
|
||||
WITH_SERVERLOG=YES
|
||||
COMPRESS_DATA=NO
|
||||
# result
|
||||
TIMELINEID=1
|
||||
START_LSN=0/0b40c800
|
||||
STOP_LSN=0/0b4c8020
|
||||
START_TIME='2009-06-03 17:05:53'
|
||||
END_TIME='****-**-** **:**:**'
|
||||
TOTAL_DATA_BYTES=-1
|
||||
READ_DATA_BYTES=-1
|
||||
READ_ARCLOG_BYTES=-1
|
||||
READ_SRVLOG_BYTES=-1
|
||||
WRITE_BYTES=-1
|
||||
BLOCK_SIZE=8192
|
||||
XLOG_BLOCK_SIZE=8192
|
||||
STATUS=RUNNING
|
0
data/sample_backup/20090603/170553/file_arclog.txt
Normal file
0
data/sample_backup/20090603/170553/file_arclog.txt
Normal file
0
data/sample_backup/20090603/170553/file_srvlog.txt
Normal file
0
data/sample_backup/20090603/170553/file_srvlog.txt
Normal file
0
data/sample_backup/pg_rman.ini
Normal file
0
data/sample_backup/pg_rman.ini
Normal file
235
delete.c
Normal file
235
delete.c
Normal file
@ -0,0 +1,235 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* delete.c: delete backup files.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
static int pgBackupDeleteFiles(pgBackup *backup);
|
||||
|
||||
int
|
||||
do_delete(pgBackupRange *range)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
parray *backup_list;
|
||||
bool do_delete;
|
||||
|
||||
/* DATE are always required */
|
||||
if (!pgBackupRangeIsValid(range))
|
||||
elog(ERROR_ARGS, _("required delete range option not specified: delete DATE"));
|
||||
|
||||
/* get exclusive lock of backup catalog */
|
||||
ret = catalog_lock();
|
||||
if (ret == -1)
|
||||
elog(ERROR_SYSTEM, _("can't lock backup catalog."));
|
||||
else if (ret == 1)
|
||||
elog(ERROR_ALREADY_RUNNING,
|
||||
_("another pg_rman is running, stop restore."));
|
||||
|
||||
/* get list of backups. */
|
||||
backup_list = catalog_get_backup_list(NULL);
|
||||
|
||||
do_delete = false;
|
||||
/* find delete target backup. */
|
||||
for (i = 0; i < parray_num(backup_list); i++)
|
||||
{
|
||||
pgBackup *backup = (pgBackup *)parray_get(backup_list, i);
|
||||
|
||||
/* delete backup and update status to DELETED */
|
||||
if (do_delete)
|
||||
{
|
||||
pgBackupDeleteFiles(backup);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* find latest full backup. */
|
||||
if (backup->backup_mode >= BACKUP_MODE_FULL &&
|
||||
backup->status == BACKUP_STATUS_OK &&
|
||||
backup->start_time <= range->begin)
|
||||
do_delete = true;
|
||||
}
|
||||
|
||||
/* release catalog lock */
|
||||
catalog_unlock();
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(backup_list, pgBackupFree);
|
||||
parray_free(backup_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete backups that are older than KEEP_xxx_DAYS and have more generations
|
||||
* than KEEP_xxx_FILES.
|
||||
*/
|
||||
void
|
||||
pgBackupDelete(int keep_generations, int keep_days)
|
||||
{
|
||||
int i;
|
||||
parray *backup_list;
|
||||
int backup_num;
|
||||
time_t days_threashold = current.start_time - (keep_days * 60 * 60 * 24);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
char generations_str[100];
|
||||
char days_str[100];
|
||||
|
||||
if (keep_generations == KEEP_INFINITE)
|
||||
strncpy(generations_str, "INFINITE",
|
||||
lengthof(generations_str));
|
||||
else
|
||||
snprintf(generations_str, lengthof(generations_str),
|
||||
"%d", keep_generations);
|
||||
|
||||
if (keep_days == KEEP_INFINITE)
|
||||
strncpy(days_str, "INFINITE", lengthof(days_str));
|
||||
else
|
||||
snprintf(days_str, lengthof(days_str), "%d", keep_days);
|
||||
|
||||
printf(_("delete old backups (generations=%s, days=%s)\n"),
|
||||
generations_str, days_str);
|
||||
}
|
||||
|
||||
/* delete files which satisfy both condition */
|
||||
if (keep_generations == KEEP_INFINITE || keep_days == KEEP_INFINITE)
|
||||
{
|
||||
elog(LOG, "%s() infinite", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* get list of backups. */
|
||||
backup_list = catalog_get_backup_list(NULL);
|
||||
|
||||
backup_num = 0;
|
||||
/* find delete target backup. */
|
||||
for (i = 0; i < parray_num(backup_list); i++)
|
||||
{
|
||||
pgBackup *backup = (pgBackup *)parray_get(backup_list, i);
|
||||
|
||||
elog(LOG, "%s() %lu", __FUNCTION__, backup->start_time);
|
||||
/*
|
||||
* when validate full backup was found, we can delete the backup
|
||||
* that is older than it
|
||||
*/
|
||||
if (backup->backup_mode >= BACKUP_MODE_FULL &&
|
||||
backup->status == BACKUP_STATUS_OK)
|
||||
backup_num++;
|
||||
|
||||
/* do not include the latest full backup in a count. */
|
||||
if (backup_num - 1 <= keep_generations)
|
||||
{
|
||||
elog(LOG, "%s() backup are only %d", __FUNCTION__, backup_num);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the start time of the backup is older than the threashold and
|
||||
* there are enough generations of full backups, delete the backup.
|
||||
*/
|
||||
if (backup->start_time >= days_threashold)
|
||||
{
|
||||
elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__,
|
||||
backup->start_time, days_threashold);
|
||||
continue;
|
||||
}
|
||||
|
||||
elog(LOG, "%s() %lu is older than %lu", __FUNCTION__,
|
||||
backup->start_time, days_threashold);
|
||||
|
||||
/* delete backup and update status to DELETED */
|
||||
pgBackupDeleteFiles(backup);
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(backup_list, pgBackupFree);
|
||||
parray_free(backup_list);
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete backup files of the backup and update the status of the backup to
|
||||
* BACKUP_STATUS_DELETED.
|
||||
*/
|
||||
static int
|
||||
pgBackupDeleteFiles(pgBackup *backup)
|
||||
{
|
||||
int i;
|
||||
char path[MAXPGPATH];
|
||||
char timestamp[20];
|
||||
parray *files;
|
||||
|
||||
/*
|
||||
* If the backup was deleted already, nothing to do and such situation
|
||||
* is not error.
|
||||
*/
|
||||
if (backup->status == BACKUP_STATUS_DELETED)
|
||||
return 0;
|
||||
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
|
||||
elog(INFO, _("delete: %s"), timestamp);
|
||||
|
||||
/*
|
||||
* update STATUS to BACKUP_STATUS_DELETING in preparation for the case which
|
||||
* the error occurs before deleting all backup files.
|
||||
*/
|
||||
if (!check)
|
||||
{
|
||||
backup->status = BACKUP_STATUS_DELETING;
|
||||
pgBackupWriteIni(backup);
|
||||
}
|
||||
|
||||
/* list files to be deleted */
|
||||
files = parray_new();
|
||||
pgBackupGetPath(backup, path, lengthof(path), DATABASE_DIR);
|
||||
dir_list_file(files, path, NULL, true, true);
|
||||
pgBackupGetPath(backup, path, lengthof(path), ARCLOG_DIR);
|
||||
dir_list_file(files, path, NULL, true, true);
|
||||
pgBackupGetPath(backup, path, lengthof(path), SRVLOG_DIR);
|
||||
dir_list_file(files, path, NULL, true, true);
|
||||
|
||||
/* delete leaf node first */
|
||||
parray_qsort(files, pgFileComparePathDesc);
|
||||
for (i = 0; i < parray_num(files); i++)
|
||||
{
|
||||
pgFile *file = (pgFile *) parray_get(files, i);
|
||||
|
||||
/* print progress */
|
||||
elog(LOG, _("delete file(%d/%lu) \"%s\"\n"), i + 1,
|
||||
(unsigned long) parray_num(files), file->path);
|
||||
|
||||
/* skip actual deletion in check mode */
|
||||
if (!check)
|
||||
{
|
||||
if (remove(file->path))
|
||||
{
|
||||
elog(WARNING, _("can't remove \"%s\": %s"), file->path,
|
||||
strerror(errno));
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* After deleting all of the backup files, update STATUS to
|
||||
* BACKUP_STATUS_DELETED.
|
||||
*/
|
||||
if (!check)
|
||||
{
|
||||
backup->status = BACKUP_STATUS_DELETED;
|
||||
pgBackupWriteIni(backup);
|
||||
}
|
||||
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
|
||||
return 0;
|
||||
}
|
567
dir.c
Normal file
567
dir.c
Normal file
@ -0,0 +1,567 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* dir.c: directory operation utility.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.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, /* srvlog_path will be set later */
|
||||
NULL, /* centinel */
|
||||
};
|
||||
|
||||
static pgFile *pgFileNew(const char *path, bool omit_symlink);
|
||||
|
||||
/* 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);
|
||||
if (access(parent, F_OK) == -1)
|
||||
dir_create_dir(parent, mode);
|
||||
if (mkdir(dir, mode) == -1)
|
||||
{
|
||||
if (errno == EEXIST) /* already exist */
|
||||
return 0;
|
||||
elog(ERROR_SYSTEM, _("can't 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, _("can't 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;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, _("can't remove directory \"%s\": %s"),
|
||||
file->path, strerror(errno));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
delete_file:
|
||||
if (remove(file->path) == -1)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
return;
|
||||
elog(ERROR_SYSTEM, _("can't 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, _("can't open file \"%s\": %s"),
|
||||
file->path, strerror(errno));
|
||||
|
||||
/* calc CRC of backup file */
|
||||
INIT_CRC32(crc);
|
||||
while ((len = fread(buf, 1, sizeof(buf), fp)) == sizeof(buf))
|
||||
{
|
||||
if (interrupted)
|
||||
elog(ERROR_INTERRUPTED, _("interrupted during CRC calculation"));
|
||||
COMP_CRC32(crc, buf, len);
|
||||
}
|
||||
errno_tmp = errno;
|
||||
if (!feof(fp))
|
||||
elog(WARNING, _("can't read \"%s\": %s"), file->path,
|
||||
strerror(errno_tmp));
|
||||
if (len > 0)
|
||||
COMP_CRC32(crc, buf, len);
|
||||
FIN_CRC32(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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
pgFile *file;
|
||||
|
||||
file = pgFileNew(root, omit_symlink);
|
||||
if (file == NULL)
|
||||
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, _("can't 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 *dnamep;
|
||||
char absolute[MAXPGPATH];
|
||||
|
||||
strncpy(dname, file->path, lengthof(dname));
|
||||
dnamep = dirname(dname);
|
||||
snprintf(absolute, lengthof(absolute), "%s/%s", dname, linked);
|
||||
file = pgFileNew(absolute, omit_symlink);
|
||||
}
|
||||
else
|
||||
file = pgFileNew(file->linked, omit_symlink);
|
||||
|
||||
/* linked file is not found, stop chasing 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 th
|
||||
* 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, _("can't 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;
|
||||
|
||||
snprintf(child, lengthof(child), "%s/%s", file->path, dent->d_name);
|
||||
dir_list_file(files, child, exclude, omit_symlink, true);
|
||||
}
|
||||
if (errno && errno != ENOENT)
|
||||
{
|
||||
int errno_tmp = errno;
|
||||
closedir(dir);
|
||||
elog(ERROR_SYSTEM, _("can't read directory \"%s\": %s"),
|
||||
file->path, strerror(errno_tmp));
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
break; /* psuedo 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)
|
||||
{
|
||||
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 = file->path;
|
||||
char type;
|
||||
|
||||
/* omit root directory portion */
|
||||
if (root && strstr(path, root) == path)
|
||||
path = path + root_len;
|
||||
|
||||
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,
|
||||
_("can't 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
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];
|
||||
snprintf(to_path, lengthof(to_path), "%s/%s", to_root,
|
||||
file->path + strlen(from_root) + 1);
|
||||
if (verbose && !check)
|
||||
printf(_("create directory \"%s\"\n"),
|
||||
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)
|
||||
printf(_("copy \"%s\"\n"),
|
||||
file->path + strlen(from_root) + 1);
|
||||
if (!check)
|
||||
copy_file(from_root, to_root, file, NO_COMPRESSION);
|
||||
}
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
}
|
40
expected/backup_restore.out
Normal file
40
expected/backup_restore.out
Normal file
@ -0,0 +1,40 @@
|
||||
\! sh sql/backup_restore.sh
|
||||
Line style is ascii.
|
||||
CREATE TABLESPACE
|
||||
CREATE DATABASE
|
||||
# of deleted backups
|
||||
0
|
||||
full database backup
|
||||
CHECKPOINT
|
||||
incremental database backup
|
||||
CHECKPOINT
|
||||
CHECKPOINT
|
||||
archived WAL and serverlog backup
|
||||
stop DB during running pgbench
|
||||
diff files in BACKUP_PATH/backup/pg_xlog
|
||||
# of files in BACKUP_PATH/backup/srvlog
|
||||
1
|
||||
diff files in BACKUP_PATH/backup/pg_xlog
|
||||
# of files in BACKUP_PATH/backup/srvlog
|
||||
2
|
||||
full database backup after recovery
|
||||
CHECKPOINT
|
||||
# of files in BACKUP_PATH/backup/pg_xlog
|
||||
0
|
||||
# of files in BACKUP_PATH/backup/srvlog
|
||||
2
|
||||
# of symbolic links in ARCLOG_PATH
|
||||
0
|
||||
# of files in BACKUP_PATH/timeline_history
|
||||
2
|
||||
# of recovery target option in recovery.conf
|
||||
3
|
||||
# of deleted backups (show all)
|
||||
4
|
||||
# of deleted backups
|
||||
0
|
||||
delete backup
|
||||
# of deleted backups
|
||||
4
|
||||
# of deleted backups
|
||||
9
|
14
expected/init.out
Normal file
14
expected/init.out
Normal file
@ -0,0 +1,14 @@
|
||||
\! rm -rf results/init_test
|
||||
\! pg_rman init -B results/init_test --quiet;echo $?
|
||||
WARNING: ARCLOG_PATH is not set because archive_command is empty
|
||||
0
|
||||
\! find results/init_test | xargs ls -Fd | sort
|
||||
results/init_test/
|
||||
results/init_test/backup/
|
||||
results/init_test/backup/pg_xlog/
|
||||
results/init_test/backup/srvlog/
|
||||
results/init_test/pg_rman.ini
|
||||
results/init_test/timeline_history/
|
||||
\! pg_rman init -B results/init_test --quiet;echo $?
|
||||
ERROR: backup catalog already exist.
|
||||
2
|
80
expected/option.out
Normal file
80
expected/option.out
Normal file
@ -0,0 +1,80 @@
|
||||
\! sh sql/option.sh
|
||||
pg_rman manage backup/recovery of PostgreSQL database.
|
||||
|
||||
Usage:
|
||||
pg_rman OPTION init
|
||||
pg_rman OPTION backup
|
||||
pg_rman OPTION restore
|
||||
pg_rman OPTION show [DATE]
|
||||
pg_rman OPTION show timeline [DATE]
|
||||
pg_rman OPTION validate [DATE]
|
||||
pg_rman OPTION delete DATE
|
||||
|
||||
Common Options:
|
||||
-D, --pgdata=PATH location of the database storage area
|
||||
-A, --arclog-path=PATH location of archive WAL storage area
|
||||
-S, --srvlog-path=PATH location of server log storage area
|
||||
-B, --backup-path=PATH location of the backup storage area
|
||||
-c, --check show what would have been done
|
||||
|
||||
Backup options:
|
||||
-b, --backup-mode=MODE full, incremental, or archive
|
||||
-s, --with-serverlog also backup server log files
|
||||
-Z, --compress-data compress data backup with zlib
|
||||
-C, --smooth-checkpoint do smooth checkpoint before backup
|
||||
--keep-data-generations=N keep GENERATION of full data backup
|
||||
--keep-data-days=DAY keep enough data backup to recover to DAY days age
|
||||
--keep-arclog-files=NUM keep NUM of archived WAL
|
||||
--keep-arclog-days=DAY keep archived WAL modified in DAY days
|
||||
--keep-srvlog-files=NUM keep NUM of serverlogs
|
||||
--keep-srvlog-days=DAY keep serverlog modified in DAY days
|
||||
|
||||
Restore options:
|
||||
--recovery-target-time time stamp up to which recovery will proceed
|
||||
--recovery-target-xid transaction ID up to which recovery will proceed
|
||||
--recovery-target-inclusive whether we stop just after the recovery target
|
||||
--recovery-target-timeline recovering into a particular timeline
|
||||
|
||||
Catalog options:
|
||||
-a, --show-all show deleted backup too
|
||||
|
||||
Connection options:
|
||||
-d, --dbname=DBNAME database to connect
|
||||
-h, --host=HOSTNAME database server host or socket directory
|
||||
-p, --port=PORT database server port
|
||||
-U, --username=USERNAME user name to connect as
|
||||
-w, --no-password never prompt for password
|
||||
-W, --password force password prompt
|
||||
|
||||
Generic options:
|
||||
-q, --quiet don't write any messages
|
||||
--debug debug mode
|
||||
--help show this help, then exit
|
||||
--version output version information, then exit
|
||||
|
||||
Read the website for details. <http://rman.projects.postgresql.org/>
|
||||
Report bugs to <rman-general@lists.pgfoundry.org>.
|
||||
pg_rman 1.1.0
|
||||
ERROR: required parameter not specified: BACKUP_PATH (-B, --backup-path)
|
||||
ERROR: required parameter not specified: BACKUP_MODE (-b, --backup-mode)
|
||||
ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path)
|
||||
ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path)
|
||||
ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path)
|
||||
ERROR: invalid backup-mode "bad"
|
||||
ERROR: required delete range option not specified: delete DATE
|
||||
INFO: validate: 2009-05-31 17:05:53
|
||||
INFO: validate: 2009-06-01 17:05:53
|
||||
WARNING: CRC of backup file "PG_VERSION" must be 0 but FEF71BC1
|
||||
WARNING: backup 2009-06-01 17:05:53 is corrupted
|
||||
WARNING: syntax error in " = INFINITE".
|
||||
ERROR: required parameter not specified: BACKUP_MODE (-b, --backup-mode)
|
||||
ERROR: invalid backup-mode ""
|
||||
ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path)
|
||||
ERROR: required parameter not specified: ARCLOG_PATH (-A, --arclog-path)
|
||||
ERROR: invalid backup-mode "B"
|
||||
ERROR: option -Z, --compress-data should be a boolean: 'FOO'
|
||||
ERROR: option --keep-arclog-files should be a 32bit signed integer: 'YES'
|
||||
ERROR: invalid option "TIMELINEID"
|
||||
ERROR: invalid option "BACKUP_TARGETS"
|
||||
ERROR: invalid backup-mode "F''\F"
|
||||
ERROR: invalid backup-mode "ENV_PATH"
|
50
expected/show_validate.out
Normal file
50
expected/show_validate.out
Normal file
@ -0,0 +1,50 @@
|
||||
-- test show command
|
||||
\! rm -rf results/sample_backup
|
||||
\! cp -rp data/sample_backup results/sample_backup
|
||||
\! pg_rman show -B results/sample_backup
|
||||
============================================================================
|
||||
Start Time Total Data WAL Log Backup Status
|
||||
============================================================================
|
||||
2009-06-03 17:05:53 ---- ---- ---- ---- ---- ---- RUNNING
|
||||
2009-06-01 17:05:53 3m ---- 9223PB 16MB ---- 162MB DONE
|
||||
2009-05-31 17:05:53 3m 1242MB ---- 9223PB ---- 242MB DONE
|
||||
\! pg_rman validate -B results/sample_backup 2009-05-31 17:05:53 --debug
|
||||
INFO: validate: 2009-05-31 17:05:53
|
||||
LOG: database files...
|
||||
LOG: (1/1) PG_VERSION
|
||||
LOG: archive WAL files...
|
||||
LOG: backup 2009-05-31 17:05:53 is valid
|
||||
\! pg_rman validate -B results/sample_backup 2009-06-01 17:05:53 --debug
|
||||
INFO: validate: 2009-06-01 17:05:53
|
||||
LOG: database files...
|
||||
LOG: (1/1) PG_VERSION
|
||||
WARNING: CRC of backup file "PG_VERSION" must be 0 but FEF71BC1
|
||||
LOG: archive WAL files...
|
||||
WARNING: backup 2009-06-01 17:05:53 is corrupted
|
||||
\! pg_rman show -a -B results/sample_backup
|
||||
============================================================================
|
||||
Start Time Total Data WAL Log Backup Status
|
||||
============================================================================
|
||||
2009-06-03 17:05:53 ---- ---- ---- ---- ---- ---- RUNNING
|
||||
2009-06-02 17:05:03 0m ---- ---- ---- 4335kB 162MB DELETED
|
||||
2009-06-01 17:05:53 3m ---- 9223PB 16MB ---- 162MB CORRUPT
|
||||
2009-05-31 17:05:53 3m 1242MB ---- 9223PB ---- 242MB OK
|
||||
\! pg_rman show 2009-06-01 17:05:53 -B results/sample_backup
|
||||
# configuration
|
||||
BACKUP_MODE=INCREMENTAL
|
||||
WITH_SERVERLOG=false
|
||||
COMPRESS_DATA=false
|
||||
# result
|
||||
TIMELINEID=1
|
||||
START_LSN=0/0b40c800
|
||||
STOP_LSN=0/0b4c8020
|
||||
START_TIME='2009-06-01 17:05:53'
|
||||
END_TIME='2009-06-01 17:09:13'
|
||||
TOTAL_DATA_BYTES=1242102558
|
||||
READ_DATA_BYTES=9223372036854775807
|
||||
READ_ARCLOG_BYTES=16777216
|
||||
READ_SRVLOG_BYTES=-1
|
||||
WRITE_BYTES=162372983
|
||||
BLOCK_SIZE=8192
|
||||
XLOG_BLOCK_SIZE=8192
|
||||
STATUS=CORRUPT
|
156
init.c
Normal file
156
init.c
Normal file
@ -0,0 +1,156 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* init.c: manage backup catalog.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
static void parse_postgresql_conf(const char *path, char **log_directory,
|
||||
char **archive_command);
|
||||
|
||||
/*
|
||||
* Initialize backup catalog.
|
||||
*/
|
||||
int
|
||||
do_init(void)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
char *log_directory = NULL;
|
||||
char *archive_command = NULL;
|
||||
FILE *fp;
|
||||
|
||||
if (access(backup_path, F_OK) == 0)
|
||||
elog(ERROR, _("backup catalog already exist."));
|
||||
|
||||
/* create backup catalog root directory */
|
||||
dir_create_dir(backup_path, DIR_PERMISSION);
|
||||
|
||||
/* create directories for backup of online files */
|
||||
snprintf(path, lengthof(path), "%s/%s", backup_path, RESTORE_WORK_DIR);
|
||||
dir_create_dir(path, DIR_PERMISSION);
|
||||
snprintf(path, lengthof(path), "%s/%s/%s", backup_path, RESTORE_WORK_DIR,
|
||||
PG_XLOG_DIR);
|
||||
dir_create_dir(path, DIR_PERMISSION);
|
||||
snprintf(path, lengthof(path), "%s/%s/%s", backup_path, RESTORE_WORK_DIR,
|
||||
SRVLOG_DIR);
|
||||
dir_create_dir(path, DIR_PERMISSION);
|
||||
|
||||
/* create directory for timeline history files */
|
||||
snprintf(path, lengthof(path), "%s/%s", backup_path, TIMELINE_HISTORY_DIR);
|
||||
dir_create_dir(path, DIR_PERMISSION);
|
||||
|
||||
/* read postgresql.conf */
|
||||
if (pgdata)
|
||||
{
|
||||
snprintf(path, lengthof(path), "%s/%s", pgdata, "postgresql.conf");
|
||||
parse_postgresql_conf(path, &log_directory, &archive_command);
|
||||
}
|
||||
|
||||
/* create pg_rman.ini */
|
||||
snprintf(path, lengthof(path), "%s/%s", backup_path, PG_RMAN_INI_FILE);
|
||||
fp = fopen(path, "wt");
|
||||
if (fp == NULL)
|
||||
elog(ERROR_SYSTEM, _("can't create pg_rman.ini: %s"), strerror(errno));
|
||||
|
||||
/* set ARCLOG_PATH refered with log_directory */
|
||||
if (arclog_path == NULL && archive_command && archive_command[0])
|
||||
{
|
||||
char *command = pgut_strdup(archive_command);
|
||||
char *begin;
|
||||
char *end;
|
||||
char *fname;
|
||||
|
||||
/* example: 'cp "%p" /path/to/arclog/"%f"' */
|
||||
for (begin = command; *begin;)
|
||||
{
|
||||
begin = begin + strspn(begin, " \n\r\t\v");
|
||||
end = begin + strcspn(begin, " \n\r\t\v");
|
||||
*end = '\0';
|
||||
|
||||
if ((fname = strstr(begin, "%f")) != NULL)
|
||||
{
|
||||
while (strchr(" \n\r\t\v""'", *begin))
|
||||
begin++;
|
||||
fname--;
|
||||
while (fname > begin && strchr(" \n\r\t\v\"'/", fname[-1]))
|
||||
fname--;
|
||||
*fname = '\0';
|
||||
|
||||
if (is_absolute_path(begin))
|
||||
arclog_path = pgut_strdup(begin);
|
||||
break;
|
||||
}
|
||||
|
||||
begin = end + 1;
|
||||
}
|
||||
|
||||
free(command);
|
||||
}
|
||||
if (arclog_path)
|
||||
{
|
||||
fprintf(fp, "ARCLOG_PATH='%s'\n", arclog_path);
|
||||
elog(INFO, "ARCLOG_PATH is set to '%s'", arclog_path);
|
||||
}
|
||||
else if (archive_command && archive_command[0])
|
||||
elog(WARNING, "ARCLOG_PATH is not set because failed to parse archive_command '%s'", archive_command);
|
||||
else
|
||||
elog(WARNING, "ARCLOG_PATH is not set because archive_command is empty");
|
||||
|
||||
/* set SRVLOG_PATH refered with log_directory */
|
||||
if (srvlog_path == NULL)
|
||||
{
|
||||
if (log_directory)
|
||||
{
|
||||
if (is_absolute_path(log_directory))
|
||||
srvlog_path = pgut_strdup(log_directory);
|
||||
else
|
||||
{
|
||||
srvlog_path = pgut_malloc(MAXPGPATH);
|
||||
snprintf(srvlog_path, MAXPGPATH, "%s/%s", pgdata, log_directory);
|
||||
}
|
||||
}
|
||||
else if (pgdata)
|
||||
{
|
||||
/* default: log_directory = 'pg_log' */
|
||||
srvlog_path = pgut_malloc(MAXPGPATH);
|
||||
snprintf(srvlog_path, MAXPGPATH, "%s/%s", pgdata, "pg_log");
|
||||
}
|
||||
}
|
||||
if (srvlog_path)
|
||||
{
|
||||
fprintf(fp, "SRVLOG_PATH='%s'\n", srvlog_path);
|
||||
elog(INFO, "SRVLOG_PATH is set to '%s'", srvlog_path);
|
||||
}
|
||||
|
||||
fprintf(fp, "\n");
|
||||
fclose(fp);
|
||||
|
||||
free(archive_command);
|
||||
free(log_directory);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
parse_postgresql_conf(const char *path,
|
||||
char **log_directory,
|
||||
char **archive_command)
|
||||
{
|
||||
pgut_option options[] =
|
||||
{
|
||||
{ 's', 0, "log_directory" , NULL, SOURCE_ENV },
|
||||
{ 's', 0, "archive_command" , NULL, SOURCE_ENV },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
options[0].var = log_directory;
|
||||
options[1].var = archive_command;
|
||||
|
||||
pgut_readopt(path, options, LOG); /* ignore unknown options */
|
||||
}
|
184
parray.c
Normal file
184
parray.c
Normal file
@ -0,0 +1,184 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* parray.c: pointer array collection.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
/* members of struct parray are hidden from client. */
|
||||
struct parray
|
||||
{
|
||||
void **data; /* poiter array, expanded if necessary */
|
||||
size_t alloced; /* number of elements allocated */
|
||||
size_t used; /* number of elements in use */
|
||||
};
|
||||
|
||||
/*
|
||||
* Create new parray object.
|
||||
* Never returns NULL.
|
||||
*/
|
||||
parray *
|
||||
parray_new(void)
|
||||
{
|
||||
parray *a = pgut_malloc(sizeof(parray));
|
||||
|
||||
a->data = NULL;
|
||||
a->used = 0;
|
||||
a->alloced = 0;
|
||||
|
||||
parray_expand(a, 1024);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand array pointed by data to newsize.
|
||||
* Elements in expanded area are initialized to NULL.
|
||||
* Note: never returns NULL.
|
||||
*/
|
||||
void
|
||||
parray_expand(parray *array, size_t newsize)
|
||||
{
|
||||
void **p;
|
||||
|
||||
/* already allocated */
|
||||
if (newsize <= array->alloced)
|
||||
return;
|
||||
|
||||
p = pgut_realloc(array->data, sizeof(void *) * newsize);
|
||||
|
||||
/* initialize expanded area to NULL */
|
||||
memset(p + array->alloced, 0, (newsize - array->alloced) * sizeof(void *));
|
||||
|
||||
array->alloced = newsize;
|
||||
array->data = p;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
parray_free(parray *array)
|
||||
{
|
||||
if (array == NULL)
|
||||
return;
|
||||
free(array->data);
|
||||
free(array);
|
||||
}
|
||||
|
||||
void
|
||||
parray_append(parray *array, void *elem)
|
||||
{
|
||||
if (array->used + 1 > array->alloced)
|
||||
parray_expand(array, array->alloced * 2);
|
||||
|
||||
array->data[array->used++] = elem;
|
||||
}
|
||||
|
||||
void
|
||||
parray_insert(parray *array, size_t index, void *elem)
|
||||
{
|
||||
if (array->used + 1 > array->alloced)
|
||||
parray_expand(array, array->alloced * 2);
|
||||
|
||||
memmove(array->data + index + 1, array->data + index,
|
||||
(array->alloced - index - 1) * sizeof(void *));
|
||||
array->data[index] = elem;
|
||||
|
||||
/* adjust used count */
|
||||
if (array->used < index + 1)
|
||||
array->used = index + 1;
|
||||
else
|
||||
array->used++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Concatinate two parray.
|
||||
* parray_concat() appends the copy of the content of src to the end of dest.
|
||||
*/
|
||||
parray *
|
||||
parray_concat(parray *dest, const parray *src)
|
||||
{
|
||||
/* expand head array */
|
||||
parray_expand(dest, dest->used + src->used);
|
||||
|
||||
/* copy content of src after content of dest */
|
||||
memcpy(dest->data + dest->used * sizeof(void *), src->data,
|
||||
src->used * sizeof(void *));
|
||||
dest->used += parray_num(src);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
void
|
||||
parray_set(parray *array, size_t index, void *elem)
|
||||
{
|
||||
if (index > array->alloced - 1)
|
||||
parray_expand(array, index + 1);
|
||||
|
||||
array->data[index] = elem;
|
||||
|
||||
/* adjust used count */
|
||||
if (array->used < index + 1)
|
||||
array->used = index + 1;
|
||||
}
|
||||
|
||||
void *
|
||||
parray_get(const parray *array, size_t index)
|
||||
{
|
||||
if (index > array->alloced - 1)
|
||||
return NULL;
|
||||
return array->data[index];
|
||||
}
|
||||
|
||||
void *
|
||||
parray_remove(parray *array, size_t index)
|
||||
{
|
||||
void *val;
|
||||
|
||||
/* removing unused element */
|
||||
if (index > array->used)
|
||||
return NULL;
|
||||
|
||||
val = array->data[index];
|
||||
|
||||
/* Do not move if the last element was removed. */
|
||||
if (index < array->alloced - 1)
|
||||
memmove(array->data + index, array->data + index + 1,
|
||||
(array->alloced - index - 1) * sizeof(void *));
|
||||
|
||||
/* adjust used count */
|
||||
array->used--;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
size_t
|
||||
parray_num(const parray *array)
|
||||
{
|
||||
return array->used;
|
||||
}
|
||||
|
||||
void
|
||||
parray_qsort(parray *array, int(*compare)(const void *, const void *))
|
||||
{
|
||||
qsort(array->data, array->used, sizeof(void *), compare);
|
||||
}
|
||||
|
||||
void
|
||||
parray_walk(parray *array, void (*action)(void *))
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < array->used; i++)
|
||||
action(array->data[i]);
|
||||
}
|
||||
|
||||
void *
|
||||
parray_bsearch(parray *array, const void *key, int(*compare)(const void *, const void *))
|
||||
{
|
||||
return bsearch(&key, array->data, array->used, sizeof(void *), compare);
|
||||
}
|
||||
|
34
parray.h
Normal file
34
parray.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* parray.h: pointer array collection.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef PARRAY_H
|
||||
#define PARRAY_H
|
||||
|
||||
/*
|
||||
* "parray" hold pointers to objects in a linear memory area.
|
||||
* Client use "parray *" to access parray object.
|
||||
*/
|
||||
typedef struct parray parray;
|
||||
|
||||
extern parray *parray_new(void);
|
||||
extern void parray_expand(parray *array, size_t newnum);
|
||||
extern void parray_free(parray *array);
|
||||
extern void parray_append(parray *array, void *val);
|
||||
extern void parray_insert(parray *array, size_t index, void *val);
|
||||
extern parray *parray_concat(parray *head, const parray *tail);
|
||||
extern void parray_set(parray *array, size_t index, void *val);
|
||||
extern void *parray_get(const parray *array, size_t index);
|
||||
extern void *parray_remove(parray *array, size_t index);
|
||||
extern size_t parray_num(const parray *array);
|
||||
extern void parray_qsort(parray *array, int(*compare)(const void *, const void *));
|
||||
extern void *parray_bsearch(parray *array, const void *key, int(*compare)(const void *, const void *));
|
||||
extern void parray_walk(parray *array, void (*action)(void *));
|
||||
|
||||
#endif /* PARRAY_H */
|
||||
|
301
pg_rman.c
Normal file
301
pg_rman.c
Normal file
@ -0,0 +1,301 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pg_rman.c: Backup/Recovery manager for PostgreSQL.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
const char *PROGRAM_VERSION = "1.1.1";
|
||||
const char *PROGRAM_URL = "https://code.google.com/p/pg-rman/";
|
||||
const char *PROGRAM_EMAIL = "https://code.google.com/p/pg-rman/issues/list";
|
||||
|
||||
/* path configuration */
|
||||
char *backup_path;
|
||||
char *pgdata;
|
||||
char *arclog_path;
|
||||
char *srvlog_path;
|
||||
|
||||
/* common configuration */
|
||||
bool verbose = false;
|
||||
bool check = false;
|
||||
|
||||
/* directory configuration */
|
||||
pgBackup current;
|
||||
|
||||
/* backup configuration */
|
||||
static bool smooth_checkpoint;
|
||||
static int keep_arclog_files = KEEP_INFINITE;
|
||||
static int keep_arclog_days = KEEP_INFINITE;
|
||||
static int keep_srvlog_files = KEEP_INFINITE;
|
||||
static int keep_srvlog_days = KEEP_INFINITE;
|
||||
static int keep_data_generations = KEEP_INFINITE;
|
||||
static int keep_data_days = KEEP_INFINITE;
|
||||
|
||||
/* restore configuration */
|
||||
static char *target_time;
|
||||
static char *target_xid;
|
||||
static char *target_inclusive;
|
||||
static TimeLineID target_tli;
|
||||
|
||||
/* show configuration */
|
||||
static bool show_all = false;
|
||||
|
||||
static void opt_backup_mode(pgut_option *opt, const char *arg);
|
||||
static void parse_range(pgBackupRange *range, const char *arg1, const char *arg2);
|
||||
|
||||
static pgut_option options[] =
|
||||
{
|
||||
/* directory options */
|
||||
{ 's', 'D', "pgdata" , &pgdata , SOURCE_ENV },
|
||||
{ 's', 'A', "arclog-path" , &arclog_path , SOURCE_ENV },
|
||||
{ 's', 'B', "backup-path" , &backup_path , SOURCE_ENV },
|
||||
{ 's', 'S', "srvlog-path" , &srvlog_path , SOURCE_ENV },
|
||||
/* common options */
|
||||
{ 'b', 'v', "verbose" , &verbose },
|
||||
{ 'b', 'c', "check" , &check },
|
||||
/* backup options */
|
||||
{ 'f', 'b', "backup-mode" , opt_backup_mode , SOURCE_ENV },
|
||||
{ 'b', 's', "with-serverlog" , ¤t.with_serverlog , SOURCE_ENV },
|
||||
{ 'b', 'Z', "compress-data" , ¤t.compress_data , SOURCE_ENV },
|
||||
{ 'b', 'C', "smooth-checkpoint" , &smooth_checkpoint , SOURCE_ENV },
|
||||
/* options with only long name (keep-xxx) */
|
||||
{ 'i', 1, "keep-data-generations" , &keep_data_generations, SOURCE_ENV },
|
||||
{ 'i', 2, "keep-data-days" , &keep_data_days , SOURCE_ENV },
|
||||
{ 'i', 3, "keep-arclog-files" , &keep_arclog_files , SOURCE_ENV },
|
||||
{ 'i', 4, "keep-arclog-days" , &keep_arclog_days , SOURCE_ENV },
|
||||
{ 'i', 5, "keep-srvlog-files" , &keep_srvlog_files , SOURCE_ENV },
|
||||
{ 'i', 6, "keep-srvlog-days" , &keep_srvlog_days , SOURCE_ENV },
|
||||
/* restore options */
|
||||
{ 's', 7, "recovery-target-time" , &target_time , SOURCE_ENV },
|
||||
{ 's', 8, "recovery-target-xid" , &target_xid , SOURCE_ENV },
|
||||
{ 's', 9, "recovery-target-inclusive" , &target_inclusive , SOURCE_ENV },
|
||||
{ 'u', 10, "recovery-target-timeline" , &target_tli , SOURCE_ENV },
|
||||
/* catalog options */
|
||||
{ 'b', 'a', "show-all" , &show_all },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
/*
|
||||
* Entry point of pg_rman command.
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
const char *cmd = NULL;
|
||||
const char *range1 = NULL;
|
||||
const char *range2 = NULL;
|
||||
bool show_timeline = false;
|
||||
pgBackupRange range;
|
||||
int i;
|
||||
|
||||
/* do not buffer progress messages */
|
||||
setvbuf(stdout, 0, _IONBF, 0); /* TODO: remove this */
|
||||
|
||||
/* initialize configuration */
|
||||
catalog_init_config(¤t);
|
||||
|
||||
/* overwrite configuration with command line arguments */
|
||||
i = pgut_getopt(argc, argv, options);
|
||||
|
||||
for (; i < argc; i++)
|
||||
{
|
||||
if (cmd == NULL)
|
||||
cmd = argv[i];
|
||||
else if (pg_strcasecmp(argv[i], "timeline") == 0 &&
|
||||
pg_strcasecmp(cmd, "show") == 0)
|
||||
show_timeline = true;
|
||||
else if (range1 == NULL)
|
||||
range1 = argv[i];
|
||||
else if (range2 == NULL)
|
||||
range2 = argv[i];
|
||||
else
|
||||
elog(ERROR_ARGS, "too many arguments");
|
||||
}
|
||||
|
||||
/* command argument (backup/restore/show/...) is required. */
|
||||
if (cmd == NULL)
|
||||
{
|
||||
help(false);
|
||||
return HELP;
|
||||
}
|
||||
|
||||
/* get object range argument if any */
|
||||
if (range1 && range2)
|
||||
parse_range(&range, range1, range2);
|
||||
else if (range1)
|
||||
parse_range(&range, range1, "");
|
||||
else
|
||||
range.begin = range.end = 0;
|
||||
|
||||
/* Read default configuration from file. */
|
||||
if (backup_path)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
|
||||
snprintf(path, lengthof(path), "%s/%s", backup_path, PG_RMAN_INI_FILE);
|
||||
pgut_readopt(path, options, ERROR_ARGS);
|
||||
}
|
||||
|
||||
/* BACKUP_PATH is always required */
|
||||
if (backup_path == NULL)
|
||||
elog(ERROR_ARGS, "required parameter not specified: BACKUP_PATH (-B, --backup-path)");
|
||||
|
||||
/* path must be absolute */
|
||||
if (pgdata != NULL && !is_absolute_path(pgdata))
|
||||
elog(ERROR_ARGS, "-D, --pgdata must be an absolute path");
|
||||
if (arclog_path != NULL && !is_absolute_path(arclog_path))
|
||||
elog(ERROR_ARGS, "-A, --arclog-path must be an absolute path");
|
||||
if (srvlog_path != NULL && !is_absolute_path(srvlog_path))
|
||||
elog(ERROR_ARGS, "-S, --srvlog-path must be an absolute path");
|
||||
|
||||
/* setup exclusion list for file search */
|
||||
for (i = 0; pgdata_exclude[i]; i++) /* find first empty slot */
|
||||
;
|
||||
if (arclog_path)
|
||||
pgdata_exclude[i++] = arclog_path;
|
||||
if (srvlog_path)
|
||||
pgdata_exclude[i++] = srvlog_path;
|
||||
|
||||
/* do actual operation */
|
||||
if (pg_strcasecmp(cmd, "init") == 0)
|
||||
return do_init();
|
||||
else if (pg_strcasecmp(cmd, "backup") == 0)
|
||||
return do_backup(smooth_checkpoint,
|
||||
keep_arclog_files, keep_arclog_days,
|
||||
keep_srvlog_files, keep_srvlog_days,
|
||||
keep_data_generations, keep_data_days);
|
||||
else if (pg_strcasecmp(cmd, "restore") == 0)
|
||||
return do_restore(target_time, target_xid, target_inclusive, target_tli);
|
||||
else if (pg_strcasecmp(cmd, "show") == 0)
|
||||
return do_show(&range, show_timeline, show_all);
|
||||
else if (pg_strcasecmp(cmd, "validate") == 0)
|
||||
return do_validate(&range);
|
||||
else if (pg_strcasecmp(cmd, "delete") == 0)
|
||||
return do_delete(&range);
|
||||
else
|
||||
elog(ERROR_ARGS, "invalid command \"%s\"", cmd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
pgut_help(bool details)
|
||||
{
|
||||
printf(_("%s manage backup/recovery of PostgreSQL database.\n\n"), PROGRAM_NAME);
|
||||
printf(_("Usage:\n"));
|
||||
printf(_(" %s OPTION init\n"), PROGRAM_NAME);
|
||||
printf(_(" %s OPTION backup\n"), PROGRAM_NAME);
|
||||
printf(_(" %s OPTION restore\n"), PROGRAM_NAME);
|
||||
printf(_(" %s OPTION show [DATE]\n"), PROGRAM_NAME);
|
||||
printf(_(" %s OPTION show timeline [DATE]\n"), PROGRAM_NAME);
|
||||
printf(_(" %s OPTION validate [DATE]\n"), PROGRAM_NAME);
|
||||
printf(_(" %s OPTION delete DATE\n"), PROGRAM_NAME);
|
||||
|
||||
if (!details)
|
||||
return;
|
||||
|
||||
printf(_("\nCommon Options:\n"));
|
||||
printf(_(" -D, --pgdata=PATH location of the database storage area\n"));
|
||||
printf(_(" -A, --arclog-path=PATH location of archive WAL storage area\n"));
|
||||
printf(_(" -S, --srvlog-path=PATH location of server log storage area\n"));
|
||||
printf(_(" -B, --backup-path=PATH location of the backup storage area\n"));
|
||||
printf(_(" -c, --check show what would have been done\n"));
|
||||
printf(_("\nBackup options:\n"));
|
||||
printf(_(" -b, --backup-mode=MODE full, incremental, or archive\n"));
|
||||
printf(_(" -s, --with-serverlog also backup server log files\n"));
|
||||
printf(_(" -Z, --compress-data compress data backup with zlib\n"));
|
||||
printf(_(" -C, --smooth-checkpoint do smooth checkpoint before backup\n"));
|
||||
printf(_(" --keep-data-generations=N keep GENERATION of full data backup\n"));
|
||||
printf(_(" --keep-data-days=DAY keep enough data backup to recover to DAY days age\n"));
|
||||
printf(_(" --keep-arclog-files=NUM keep NUM of archived WAL\n"));
|
||||
printf(_(" --keep-arclog-days=DAY keep archived WAL modified in DAY days\n"));
|
||||
printf(_(" --keep-srvlog-files=NUM keep NUM of serverlogs\n"));
|
||||
printf(_(" --keep-srvlog-days=DAY keep serverlog modified in DAY days\n"));
|
||||
printf(_("\nRestore options:\n"));
|
||||
printf(_(" --recovery-target-time time stamp up to which recovery will proceed\n"));
|
||||
printf(_(" --recovery-target-xid transaction ID up to which recovery will proceed\n"));
|
||||
printf(_(" --recovery-target-inclusive whether we stop just after the recovery target\n"));
|
||||
printf(_(" --recovery-target-timeline recovering into a particular timeline\n"));
|
||||
printf(_("\nCatalog options:\n"));
|
||||
printf(_(" -a, --show-all show deleted backup too\n"));
|
||||
}
|
||||
|
||||
/*
|
||||
* Create range object from one or two arguments.
|
||||
* All not-digit characters in the argument(s) are igonred.
|
||||
* Both arg1 and arg2 must be valid pointer.
|
||||
*/
|
||||
static void
|
||||
parse_range(pgBackupRange *range, const char *arg1, const char *arg2)
|
||||
{
|
||||
size_t len = strlen(arg1) + strlen(arg2) + 1;
|
||||
char *tmp;
|
||||
int num;
|
||||
struct tm tm;
|
||||
|
||||
tmp = pgut_malloc(len);
|
||||
tmp[0] = '\0';
|
||||
if (arg1 != NULL)
|
||||
remove_not_digit(tmp, len, arg1);
|
||||
if (arg2 != NULL)
|
||||
remove_not_digit(tmp + strlen(tmp), len - strlen(tmp), arg2);
|
||||
|
||||
tm.tm_year = 0; /* tm_year is year - 1900 */
|
||||
tm.tm_mon = 0; /* tm_mon is 0 - 11 */
|
||||
tm.tm_mday = 1; /* tm_mday is 1 - 31 */
|
||||
tm.tm_hour = 0;
|
||||
tm.tm_min = 0;
|
||||
tm.tm_sec = 0;
|
||||
num = sscanf(tmp, "%04d %02d %02d %02d %02d %02d",
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
|
||||
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
|
||||
|
||||
if (num < 1)
|
||||
elog(ERROR_ARGS, _("supplied id(%s) is invalid."), tmp);
|
||||
|
||||
free(tmp);
|
||||
|
||||
/* adjust year and month to convert to time_t */
|
||||
tm.tm_year -= 1900;
|
||||
if (num > 1)
|
||||
tm.tm_mon -= 1;
|
||||
range->begin = mktime(&tm);
|
||||
|
||||
switch (num)
|
||||
{
|
||||
case 1:
|
||||
tm.tm_year++;
|
||||
break;
|
||||
case 2:
|
||||
tm.tm_mon++;
|
||||
break;
|
||||
case 3:
|
||||
tm.tm_mday++;
|
||||
break;
|
||||
case 4:
|
||||
tm.tm_hour++;
|
||||
break;
|
||||
case 5:
|
||||
tm.tm_min++;
|
||||
break;
|
||||
case 6:
|
||||
tm.tm_sec++;
|
||||
break;
|
||||
}
|
||||
range->end = mktime(&tm);
|
||||
range->end--;
|
||||
}
|
||||
|
||||
static void
|
||||
opt_backup_mode(pgut_option *opt, const char *arg)
|
||||
{
|
||||
current.backup_mode = parse_backup_mode(arg, ERROR_ARGS);
|
||||
}
|
283
pg_rman.h
Normal file
283
pg_rman.h
Normal file
@ -0,0 +1,283 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pg_rman.h: Backup/Recovery manager for PostgreSQL.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef PG_RMAN_H
|
||||
#define PG_RMAN_H
|
||||
|
||||
#include "postgres_fe.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include "libpq-fe.h"
|
||||
|
||||
#include "pgut/pgut.h"
|
||||
#include "access/xlogdefs.h"
|
||||
#include "utils/pg_crc.h"
|
||||
#include "parray.h"
|
||||
|
||||
/* Directory/File names */
|
||||
#define DATABASE_DIR "database"
|
||||
#define ARCLOG_DIR "arclog"
|
||||
#define SRVLOG_DIR "srvlog"
|
||||
#define RESTORE_WORK_DIR "backup"
|
||||
#define PG_XLOG_DIR "pg_xlog"
|
||||
#define TIMELINE_HISTORY_DIR "timeline_history"
|
||||
#define BACKUP_INI_FILE "backup.ini"
|
||||
#define PG_RMAN_INI_FILE "pg_rman.ini"
|
||||
#define MKDIRS_SH_FILE "mkdirs.sh"
|
||||
#define DATABASE_FILE_LIST "file_database.txt"
|
||||
#define ARCLOG_FILE_LIST "file_arclog.txt"
|
||||
#define SRVLOG_FILE_LIST "file_srvlog.txt"
|
||||
|
||||
/* Direcotry/File permission */
|
||||
#define DIR_PERMISSION (0700)
|
||||
#define FILE_PERMISSION (0600)
|
||||
|
||||
/* Exit code */
|
||||
#define ERROR_ARCHIVE_FAILED 20 /* cannot archive xlog file */
|
||||
#define ERROR_NO_BACKUP 21 /* backup was not found in the catalog */
|
||||
#define ERROR_CORRUPTED 22 /* backup catalog is corrupted */
|
||||
#define ERROR_ALREADY_RUNNING 23 /* another pg_rman is running */
|
||||
#define ERROR_PG_INCOMPATIBLE 24 /* block size is not compatible */
|
||||
#define ERROR_PG_RUNNING 25 /* PostgreSQL server is running */
|
||||
#define ERROR_PID_BROKEN 26 /* postmaster.pid file is broken */
|
||||
|
||||
/* backup mode file */
|
||||
typedef struct pgFile
|
||||
{
|
||||
time_t mtime; /* time of last modification */
|
||||
mode_t mode; /* protection (file type and permission) */
|
||||
size_t size; /* size of the file */
|
||||
size_t read_size; /* size of the portion read (if only some pages are
|
||||
backed up partially, it's different from size) */
|
||||
size_t write_size; /* size of the backed-up file. BYTES_INVALID means
|
||||
that the file existed but was not backed up
|
||||
because not modified since last backup. */
|
||||
pg_crc32 crc; /* CRC value of the file, regular file only */
|
||||
char *linked; /* path of the linked file */
|
||||
bool is_datafile; /* true if the file is PostgreSQL data file */
|
||||
char path[1]; /* path of the file */
|
||||
} pgFile;
|
||||
|
||||
typedef struct pgBackupRange
|
||||
{
|
||||
time_t begin;
|
||||
time_t end; /* begin +1 when one backup is target */
|
||||
} pgBackupRange;
|
||||
|
||||
#define pgBackupRangeIsValid(range) \
|
||||
(((range)->begin != (time_t) 0) || ((range)->end != (time_t) 0))
|
||||
#define pgBackupRangeIsSingle(range) \
|
||||
(pgBackupRangeIsValid(range) && (range)->begin == ((range)->end))
|
||||
|
||||
/* Backup status */
|
||||
/* XXX re-order ? */
|
||||
typedef enum BackupStatus
|
||||
{
|
||||
BACKUP_STATUS_INVALID, /* the pgBackup is invalid */
|
||||
BACKUP_STATUS_OK, /* completed backup */
|
||||
BACKUP_STATUS_RUNNING, /* running backup */
|
||||
BACKUP_STATUS_ERROR, /* aborted because of unexpected error */
|
||||
BACKUP_STATUS_DELETING, /* data files are being deleted */
|
||||
BACKUP_STATUS_DELETED, /* data files have been deleted */
|
||||
BACKUP_STATUS_DONE, /* completed but not validated yet */
|
||||
BACKUP_STATUS_CORRUPT /* files are corrupted, not available */
|
||||
} BackupStatus;
|
||||
|
||||
typedef enum BackupMode
|
||||
{
|
||||
BACKUP_MODE_INVALID,
|
||||
BACKUP_MODE_ARCHIVE, /* arhicve only */
|
||||
BACKUP_MODE_INCREMENTAL, /* incremental backup */
|
||||
BACKUP_MODE_FULL /* full backup */
|
||||
} BackupMode;
|
||||
|
||||
/*
|
||||
* pg_rman takes backup into the directroy $BACKUP_PATH/<date>/<time>.
|
||||
*
|
||||
* status == -1 indicates the pgBackup is invalid.
|
||||
*/
|
||||
typedef struct pgBackup
|
||||
{
|
||||
/* Backup Level */
|
||||
BackupMode backup_mode;
|
||||
bool with_serverlog;
|
||||
bool compress_data;
|
||||
|
||||
/* Status - one of BACKUP_STATUS_xxx */
|
||||
BackupStatus status;
|
||||
|
||||
/* Timestamp, etc. */
|
||||
TimeLineID tli;
|
||||
XLogRecPtr start_lsn;
|
||||
XLogRecPtr stop_lsn;
|
||||
time_t start_time;
|
||||
time_t end_time;
|
||||
|
||||
/* Size (-1 means not-backup'ed) */
|
||||
int64 total_data_bytes;
|
||||
int64 read_data_bytes;
|
||||
int64 read_arclog_bytes;
|
||||
int64 read_srvlog_bytes;
|
||||
int64 write_bytes;
|
||||
|
||||
/* data/wal block size for compatibility check */
|
||||
uint32 block_size;
|
||||
uint32 wal_block_size;
|
||||
} pgBackup;
|
||||
|
||||
/* special values of pgBackup */
|
||||
#define KEEP_INFINITE (INT_MAX)
|
||||
#define BYTES_INVALID (-1)
|
||||
|
||||
#define HAVE_DATABASE(backup) ((backup)->backup_mode >= BACKUP_MODE_INCREMENTAL)
|
||||
#define HAVE_ARCLOG(backup) ((backup)->backup_mode >= BACKUP_MODE_ARCHIVE)
|
||||
#define TOTAL_READ_SIZE(backup) \
|
||||
((HAVE_DATABASE((backup)) ? (backup)->read_data_bytes : 0) + \
|
||||
(HAVE_ARCLOG((backup)) ? (backup)->read_arclog_bytes : 0) + \
|
||||
((backup)->with_serverlog ? (backup)->read_srvlog_bytes : 0))
|
||||
|
||||
typedef struct pgTimeLine
|
||||
{
|
||||
TimeLineID tli;
|
||||
XLogRecPtr end;
|
||||
} pgTimeLine;
|
||||
|
||||
typedef enum CompressionMode
|
||||
{
|
||||
NO_COMPRESSION,
|
||||
COMPRESSION,
|
||||
DECOMPRESSION,
|
||||
} CompressionMode;
|
||||
|
||||
/* path configuration */
|
||||
extern char *backup_path;
|
||||
extern char *pgdata;
|
||||
extern char *arclog_path;
|
||||
extern char *srvlog_path;
|
||||
|
||||
/* common configuration */
|
||||
extern bool verbose;
|
||||
extern bool check;
|
||||
|
||||
/* current settings */
|
||||
extern pgBackup current;
|
||||
|
||||
/* exclude directory list for $PGDATA file listing */
|
||||
extern const char *pgdata_exclude[];
|
||||
|
||||
/* in backup.c */
|
||||
extern int do_backup(bool smooth_checkpoint,
|
||||
int keep_arclog_files,
|
||||
int keep_arclog_days,
|
||||
int keep_srvlog_files,
|
||||
int keep_srvlog_days,
|
||||
int keep_data_generations,
|
||||
int keep_data_days);
|
||||
extern BackupMode parse_backup_mode(const char *value, int elevel);
|
||||
|
||||
/* in restore.c */
|
||||
extern int do_restore(const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli);
|
||||
|
||||
/* in init.c */
|
||||
extern int do_init(void);
|
||||
|
||||
/* in show.c */
|
||||
extern int do_show(pgBackupRange *range, bool show_timeline, bool show_all);
|
||||
|
||||
/* in delete.c */
|
||||
extern int do_delete(pgBackupRange *range);
|
||||
extern void pgBackupDelete(int keep_generations, int keep_days);
|
||||
|
||||
/* in validate.c */
|
||||
extern int do_validate(pgBackupRange *range);
|
||||
extern void pgBackupValidate(pgBackup *backup, bool size_only);
|
||||
|
||||
/* in catalog.c */
|
||||
extern pgBackup *catalog_get_backup(time_t timestamp);
|
||||
extern parray *catalog_get_backup_list(const pgBackupRange *range);
|
||||
extern pgBackup *catalog_get_last_data_backup(parray *backup_list);
|
||||
extern pgBackup *catalog_get_last_arclog_backup(parray *backup_list);
|
||||
extern pgBackup *catalog_get_last_srvlog_backup(parray *backup_list);
|
||||
|
||||
extern int catalog_lock(void);
|
||||
extern void catalog_unlock(void);
|
||||
|
||||
extern void catalog_init_config(pgBackup *backup);
|
||||
|
||||
extern void pgBackupWriteConfigSection(FILE *out, pgBackup *backup);
|
||||
extern void pgBackupWriteResultSection(FILE *out, pgBackup *backup);
|
||||
extern void pgBackupWriteIni(pgBackup *backup);
|
||||
extern void pgBackupGetPath(const pgBackup *backup, char *path, size_t len, const char *subdir);
|
||||
extern int pgBackupCreateDir(pgBackup *backup);
|
||||
extern void pgBackupFree(void *backup);
|
||||
extern int pgBackupCompareId(const void *f1, const void *f2);
|
||||
extern int pgBackupCompareIdDesc(const void *f1, const void *f2);
|
||||
|
||||
/* in dir.c */
|
||||
extern void dir_list_file(parray *files, const char *root, const char *exclude[], bool omit_symlink, bool add_root);
|
||||
extern void dir_print_mkdirs_sh(FILE *out, const parray *files, const char *root);
|
||||
extern void dir_print_file_list(FILE *out, const parray *files, const char *root);
|
||||
extern parray *dir_read_file_list(const char *root, const char *file_txt);
|
||||
|
||||
extern int dir_create_dir(const char *path, mode_t mode);
|
||||
extern void dir_copy_files(const char *from_root, const char *to_root);
|
||||
|
||||
extern void pgFileDump(pgFile *file, FILE *out);
|
||||
extern void pgFileDelete(pgFile *file);
|
||||
extern void pgFileFree(void *file);
|
||||
extern pg_crc32 pgFileGetCRC(pgFile *file);
|
||||
extern int pgFileComparePath(const void *f1, const void *f2);
|
||||
extern int pgFileComparePathDesc(const void *f1, const void *f2);
|
||||
extern int pgFileCompareMtime(const void *f1, const void *f2);
|
||||
extern int pgFileCompareMtimeDesc(const void *f1, const void *f2);
|
||||
|
||||
/* in xlog.c */
|
||||
extern bool xlog_is_complete_wal(const pgFile *file);
|
||||
extern bool xlog_logfname2lsn(const char *logfname, XLogRecPtr *lsn);
|
||||
extern void xlog_fname(char *fname, size_t len, TimeLineID tli, XLogRecPtr *lsn);
|
||||
|
||||
/* in data.c */
|
||||
extern void backup_data_file(const char *from_root, const char *to_root,
|
||||
pgFile *file, const XLogRecPtr *lsn, bool compress);
|
||||
extern void restore_data_file(const char *from_root, const char *to_root,
|
||||
pgFile *file, bool compress);
|
||||
extern void copy_file(const char *from_root, const char *to_root,
|
||||
pgFile *file, CompressionMode compress);
|
||||
|
||||
/* in util.c */
|
||||
extern void time2iso(char *buf, size_t len, time_t time);
|
||||
extern const char *status2str(BackupStatus status);
|
||||
extern void remove_trailing_space(char *buf, int comment_mark);
|
||||
extern void remove_not_digit(char *buf, size_t len, const char *str);
|
||||
|
||||
/* in pgsql_src/pg_ctl.c */
|
||||
extern bool is_pg_running(void);
|
||||
|
||||
/* access/xlog_internal.h */
|
||||
#define XLogSegSize ((uint32) XLOG_SEG_SIZE)
|
||||
#define XLogSegsPerFile (((uint32) 0xffffffff) / XLogSegSize)
|
||||
#define XLogFileSize (XLogSegsPerFile * XLogSegSize)
|
||||
|
||||
#define NextLogSeg(logId, logSeg) \
|
||||
do { \
|
||||
if ((logSeg) >= XLogSegsPerFile-1) \
|
||||
{ \
|
||||
(logId)++; \
|
||||
(logSeg) = 0; \
|
||||
} \
|
||||
else \
|
||||
(logSeg)++; \
|
||||
} while (0)
|
||||
#define MAXFNAMELEN 64
|
||||
#define XLogFileName(fname, tli, log, seg) \
|
||||
snprintf(fname, MAXFNAMELEN, "%08X%08X%08X", tli, log, seg)
|
||||
|
||||
#endif /* PG_RMAN_H */
|
24
pgsql_src/COPYRIGHT.pgsql_src
Normal file
24
pgsql_src/COPYRIGHT.pgsql_src
Normal file
@ -0,0 +1,24 @@
|
||||
Files in this directory are parts of PostgreSQL Database Management System.
|
||||
Copyright holders of those files are following organizations:
|
||||
|
||||
|
||||
Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
||||
|
||||
Portions Copyright (c) 1994, The Regents of the University of California
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose, without fee, and without a written agreement
|
||||
is hereby granted, provided that the above copyright notice and this
|
||||
paragraph and the following two paragraphs appear in all copies.
|
||||
|
||||
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
|
||||
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
|
||||
LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
|
||||
DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
|
||||
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
515
pgsql_src/pg_crc.c
Normal file
515
pgsql_src/pg_crc.c
Normal file
@ -0,0 +1,515 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pg_crc.c
|
||||
* PostgreSQL CRC support
|
||||
*
|
||||
* See Ross Williams' excellent introduction
|
||||
* A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS, available from
|
||||
* http://www.ross.net/crc/download/crc_v3.txt or several other net sites.
|
||||
*
|
||||
* We use a normal (not "reflected", in Williams' terms) CRC, using initial
|
||||
* all-ones register contents and a final bit inversion.
|
||||
*
|
||||
* The 64-bit variant is not used as of PostgreSQL 8.1, but we retain the
|
||||
* code for possible future use.
|
||||
*
|
||||
*
|
||||
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/hash/pg_crc.c,v 1.21 2009/01/01 17:23:51 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* Use c.h so that this file can be built in either frontend or backend */
|
||||
#include "c.h"
|
||||
|
||||
|
||||
/*
|
||||
* This table is based on the polynomial
|
||||
* x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.
|
||||
* (This is the same polynomial used in Ethernet checksums, for instance.)
|
||||
*/
|
||||
const uint32 pg_crc32_table[256] = {
|
||||
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
|
||||
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
|
||||
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
|
||||
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
|
||||
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
|
||||
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
|
||||
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
|
||||
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
|
||||
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
||||
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
|
||||
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
|
||||
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
|
||||
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
|
||||
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
|
||||
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
|
||||
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
|
||||
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
|
||||
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
||||
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
|
||||
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
|
||||
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
|
||||
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
|
||||
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
|
||||
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
|
||||
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
|
||||
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
|
||||
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
||||
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
|
||||
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
|
||||
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
|
||||
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
|
||||
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
|
||||
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
|
||||
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
|
||||
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
|
||||
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
||||
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
|
||||
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
|
||||
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
|
||||
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
|
||||
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
|
||||
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
|
||||
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
|
||||
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
|
||||
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
||||
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
|
||||
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
|
||||
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
|
||||
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
|
||||
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
|
||||
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
|
||||
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
|
||||
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
|
||||
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
||||
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
|
||||
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
|
||||
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
|
||||
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
|
||||
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
|
||||
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
|
||||
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
|
||||
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
|
||||
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
||||
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
|
||||
};
|
||||
|
||||
|
||||
#ifdef PROVIDE_64BIT_CRC
|
||||
|
||||
/*
|
||||
* This table is based on the polynomial
|
||||
*
|
||||
* x^64 + x^62 + x^57 + x^55 + x^54 + x^53 + x^52 + x^47 + x^46 + x^45 +
|
||||
* x^40 + x^39 + x^38 + x^37 + x^35 + x^33 + x^32 + x^31 + x^29 + x^27 +
|
||||
* x^24 + x^23 + x^22 + x^21 + x^19 + x^17 + x^13 + x^12 + x^10 + x^9 +
|
||||
* x^7 + x^4 + x + 1
|
||||
*
|
||||
* which is borrowed from the DLT1 spec
|
||||
* (ECMA-182, available from http://www.ecma.ch/ecma1/STAND/ECMA-182.HTM)
|
||||
*/
|
||||
|
||||
#ifdef INT64_IS_BUSTED
|
||||
|
||||
const uint32 pg_crc64_table0[256] = {
|
||||
0x00000000, 0xA9EA3693,
|
||||
0x53D46D26, 0xFA3E5BB5,
|
||||
0x0E42ECDF, 0xA7A8DA4C,
|
||||
0x5D9681F9, 0xF47CB76A,
|
||||
0x1C85D9BE, 0xB56FEF2D,
|
||||
0x4F51B498, 0xE6BB820B,
|
||||
0x12C73561, 0xBB2D03F2,
|
||||
0x41135847, 0xE8F96ED4,
|
||||
0x90E185EF, 0x390BB37C,
|
||||
0xC335E8C9, 0x6ADFDE5A,
|
||||
0x9EA36930, 0x37495FA3,
|
||||
0xCD770416, 0x649D3285,
|
||||
0x8C645C51, 0x258E6AC2,
|
||||
0xDFB03177, 0x765A07E4,
|
||||
0x8226B08E, 0x2BCC861D,
|
||||
0xD1F2DDA8, 0x7818EB3B,
|
||||
0x21C30BDE, 0x88293D4D,
|
||||
0x721766F8, 0xDBFD506B,
|
||||
0x2F81E701, 0x866BD192,
|
||||
0x7C558A27, 0xD5BFBCB4,
|
||||
0x3D46D260, 0x94ACE4F3,
|
||||
0x6E92BF46, 0xC77889D5,
|
||||
0x33043EBF, 0x9AEE082C,
|
||||
0x60D05399, 0xC93A650A,
|
||||
0xB1228E31, 0x18C8B8A2,
|
||||
0xE2F6E317, 0x4B1CD584,
|
||||
0xBF6062EE, 0x168A547D,
|
||||
0xECB40FC8, 0x455E395B,
|
||||
0xADA7578F, 0x044D611C,
|
||||
0xFE733AA9, 0x57990C3A,
|
||||
0xA3E5BB50, 0x0A0F8DC3,
|
||||
0xF031D676, 0x59DBE0E5,
|
||||
0xEA6C212F, 0x438617BC,
|
||||
0xB9B84C09, 0x10527A9A,
|
||||
0xE42ECDF0, 0x4DC4FB63,
|
||||
0xB7FAA0D6, 0x1E109645,
|
||||
0xF6E9F891, 0x5F03CE02,
|
||||
0xA53D95B7, 0x0CD7A324,
|
||||
0xF8AB144E, 0x514122DD,
|
||||
0xAB7F7968, 0x02954FFB,
|
||||
0x7A8DA4C0, 0xD3679253,
|
||||
0x2959C9E6, 0x80B3FF75,
|
||||
0x74CF481F, 0xDD257E8C,
|
||||
0x271B2539, 0x8EF113AA,
|
||||
0x66087D7E, 0xCFE24BED,
|
||||
0x35DC1058, 0x9C3626CB,
|
||||
0x684A91A1, 0xC1A0A732,
|
||||
0x3B9EFC87, 0x9274CA14,
|
||||
0xCBAF2AF1, 0x62451C62,
|
||||
0x987B47D7, 0x31917144,
|
||||
0xC5EDC62E, 0x6C07F0BD,
|
||||
0x9639AB08, 0x3FD39D9B,
|
||||
0xD72AF34F, 0x7EC0C5DC,
|
||||
0x84FE9E69, 0x2D14A8FA,
|
||||
0xD9681F90, 0x70822903,
|
||||
0x8ABC72B6, 0x23564425,
|
||||
0x5B4EAF1E, 0xF2A4998D,
|
||||
0x089AC238, 0xA170F4AB,
|
||||
0x550C43C1, 0xFCE67552,
|
||||
0x06D82EE7, 0xAF321874,
|
||||
0x47CB76A0, 0xEE214033,
|
||||
0x141F1B86, 0xBDF52D15,
|
||||
0x49899A7F, 0xE063ACEC,
|
||||
0x1A5DF759, 0xB3B7C1CA,
|
||||
0x7D3274CD, 0xD4D8425E,
|
||||
0x2EE619EB, 0x870C2F78,
|
||||
0x73709812, 0xDA9AAE81,
|
||||
0x20A4F534, 0x894EC3A7,
|
||||
0x61B7AD73, 0xC85D9BE0,
|
||||
0x3263C055, 0x9B89F6C6,
|
||||
0x6FF541AC, 0xC61F773F,
|
||||
0x3C212C8A, 0x95CB1A19,
|
||||
0xEDD3F122, 0x4439C7B1,
|
||||
0xBE079C04, 0x17EDAA97,
|
||||
0xE3911DFD, 0x4A7B2B6E,
|
||||
0xB04570DB, 0x19AF4648,
|
||||
0xF156289C, 0x58BC1E0F,
|
||||
0xA28245BA, 0x0B687329,
|
||||
0xFF14C443, 0x56FEF2D0,
|
||||
0xACC0A965, 0x052A9FF6,
|
||||
0x5CF17F13, 0xF51B4980,
|
||||
0x0F251235, 0xA6CF24A6,
|
||||
0x52B393CC, 0xFB59A55F,
|
||||
0x0167FEEA, 0xA88DC879,
|
||||
0x4074A6AD, 0xE99E903E,
|
||||
0x13A0CB8B, 0xBA4AFD18,
|
||||
0x4E364A72, 0xE7DC7CE1,
|
||||
0x1DE22754, 0xB40811C7,
|
||||
0xCC10FAFC, 0x65FACC6F,
|
||||
0x9FC497DA, 0x362EA149,
|
||||
0xC2521623, 0x6BB820B0,
|
||||
0x91867B05, 0x386C4D96,
|
||||
0xD0952342, 0x797F15D1,
|
||||
0x83414E64, 0x2AAB78F7,
|
||||
0xDED7CF9D, 0x773DF90E,
|
||||
0x8D03A2BB, 0x24E99428,
|
||||
0x975E55E2, 0x3EB46371,
|
||||
0xC48A38C4, 0x6D600E57,
|
||||
0x991CB93D, 0x30F68FAE,
|
||||
0xCAC8D41B, 0x6322E288,
|
||||
0x8BDB8C5C, 0x2231BACF,
|
||||
0xD80FE17A, 0x71E5D7E9,
|
||||
0x85996083, 0x2C735610,
|
||||
0xD64D0DA5, 0x7FA73B36,
|
||||
0x07BFD00D, 0xAE55E69E,
|
||||
0x546BBD2B, 0xFD818BB8,
|
||||
0x09FD3CD2, 0xA0170A41,
|
||||
0x5A2951F4, 0xF3C36767,
|
||||
0x1B3A09B3, 0xB2D03F20,
|
||||
0x48EE6495, 0xE1045206,
|
||||
0x1578E56C, 0xBC92D3FF,
|
||||
0x46AC884A, 0xEF46BED9,
|
||||
0xB69D5E3C, 0x1F7768AF,
|
||||
0xE549331A, 0x4CA30589,
|
||||
0xB8DFB2E3, 0x11358470,
|
||||
0xEB0BDFC5, 0x42E1E956,
|
||||
0xAA188782, 0x03F2B111,
|
||||
0xF9CCEAA4, 0x5026DC37,
|
||||
0xA45A6B5D, 0x0DB05DCE,
|
||||
0xF78E067B, 0x5E6430E8,
|
||||
0x267CDBD3, 0x8F96ED40,
|
||||
0x75A8B6F5, 0xDC428066,
|
||||
0x283E370C, 0x81D4019F,
|
||||
0x7BEA5A2A, 0xD2006CB9,
|
||||
0x3AF9026D, 0x931334FE,
|
||||
0x692D6F4B, 0xC0C759D8,
|
||||
0x34BBEEB2, 0x9D51D821,
|
||||
0x676F8394, 0xCE85B507
|
||||
};
|
||||
|
||||
const uint32 pg_crc64_table1[256] = {
|
||||
0x00000000, 0x42F0E1EB,
|
||||
0x85E1C3D7, 0xC711223C,
|
||||
0x49336645, 0x0BC387AE,
|
||||
0xCCD2A592, 0x8E224479,
|
||||
0x9266CC8A, 0xD0962D61,
|
||||
0x17870F5D, 0x5577EEB6,
|
||||
0xDB55AACF, 0x99A54B24,
|
||||
0x5EB46918, 0x1C4488F3,
|
||||
0x663D78FF, 0x24CD9914,
|
||||
0xE3DCBB28, 0xA12C5AC3,
|
||||
0x2F0E1EBA, 0x6DFEFF51,
|
||||
0xAAEFDD6D, 0xE81F3C86,
|
||||
0xF45BB475, 0xB6AB559E,
|
||||
0x71BA77A2, 0x334A9649,
|
||||
0xBD68D230, 0xFF9833DB,
|
||||
0x388911E7, 0x7A79F00C,
|
||||
0xCC7AF1FF, 0x8E8A1014,
|
||||
0x499B3228, 0x0B6BD3C3,
|
||||
0x854997BA, 0xC7B97651,
|
||||
0x00A8546D, 0x4258B586,
|
||||
0x5E1C3D75, 0x1CECDC9E,
|
||||
0xDBFDFEA2, 0x990D1F49,
|
||||
0x172F5B30, 0x55DFBADB,
|
||||
0x92CE98E7, 0xD03E790C,
|
||||
0xAA478900, 0xE8B768EB,
|
||||
0x2FA64AD7, 0x6D56AB3C,
|
||||
0xE374EF45, 0xA1840EAE,
|
||||
0x66952C92, 0x2465CD79,
|
||||
0x3821458A, 0x7AD1A461,
|
||||
0xBDC0865D, 0xFF3067B6,
|
||||
0x711223CF, 0x33E2C224,
|
||||
0xF4F3E018, 0xB60301F3,
|
||||
0xDA050215, 0x98F5E3FE,
|
||||
0x5FE4C1C2, 0x1D142029,
|
||||
0x93366450, 0xD1C685BB,
|
||||
0x16D7A787, 0x5427466C,
|
||||
0x4863CE9F, 0x0A932F74,
|
||||
0xCD820D48, 0x8F72ECA3,
|
||||
0x0150A8DA, 0x43A04931,
|
||||
0x84B16B0D, 0xC6418AE6,
|
||||
0xBC387AEA, 0xFEC89B01,
|
||||
0x39D9B93D, 0x7B2958D6,
|
||||
0xF50B1CAF, 0xB7FBFD44,
|
||||
0x70EADF78, 0x321A3E93,
|
||||
0x2E5EB660, 0x6CAE578B,
|
||||
0xABBF75B7, 0xE94F945C,
|
||||
0x676DD025, 0x259D31CE,
|
||||
0xE28C13F2, 0xA07CF219,
|
||||
0x167FF3EA, 0x548F1201,
|
||||
0x939E303D, 0xD16ED1D6,
|
||||
0x5F4C95AF, 0x1DBC7444,
|
||||
0xDAAD5678, 0x985DB793,
|
||||
0x84193F60, 0xC6E9DE8B,
|
||||
0x01F8FCB7, 0x43081D5C,
|
||||
0xCD2A5925, 0x8FDAB8CE,
|
||||
0x48CB9AF2, 0x0A3B7B19,
|
||||
0x70428B15, 0x32B26AFE,
|
||||
0xF5A348C2, 0xB753A929,
|
||||
0x3971ED50, 0x7B810CBB,
|
||||
0xBC902E87, 0xFE60CF6C,
|
||||
0xE224479F, 0xA0D4A674,
|
||||
0x67C58448, 0x253565A3,
|
||||
0xAB1721DA, 0xE9E7C031,
|
||||
0x2EF6E20D, 0x6C0603E6,
|
||||
0xF6FAE5C0, 0xB40A042B,
|
||||
0x731B2617, 0x31EBC7FC,
|
||||
0xBFC98385, 0xFD39626E,
|
||||
0x3A284052, 0x78D8A1B9,
|
||||
0x649C294A, 0x266CC8A1,
|
||||
0xE17DEA9D, 0xA38D0B76,
|
||||
0x2DAF4F0F, 0x6F5FAEE4,
|
||||
0xA84E8CD8, 0xEABE6D33,
|
||||
0x90C79D3F, 0xD2377CD4,
|
||||
0x15265EE8, 0x57D6BF03,
|
||||
0xD9F4FB7A, 0x9B041A91,
|
||||
0x5C1538AD, 0x1EE5D946,
|
||||
0x02A151B5, 0x4051B05E,
|
||||
0x87409262, 0xC5B07389,
|
||||
0x4B9237F0, 0x0962D61B,
|
||||
0xCE73F427, 0x8C8315CC,
|
||||
0x3A80143F, 0x7870F5D4,
|
||||
0xBF61D7E8, 0xFD913603,
|
||||
0x73B3727A, 0x31439391,
|
||||
0xF652B1AD, 0xB4A25046,
|
||||
0xA8E6D8B5, 0xEA16395E,
|
||||
0x2D071B62, 0x6FF7FA89,
|
||||
0xE1D5BEF0, 0xA3255F1B,
|
||||
0x64347D27, 0x26C49CCC,
|
||||
0x5CBD6CC0, 0x1E4D8D2B,
|
||||
0xD95CAF17, 0x9BAC4EFC,
|
||||
0x158E0A85, 0x577EEB6E,
|
||||
0x906FC952, 0xD29F28B9,
|
||||
0xCEDBA04A, 0x8C2B41A1,
|
||||
0x4B3A639D, 0x09CA8276,
|
||||
0x87E8C60F, 0xC51827E4,
|
||||
0x020905D8, 0x40F9E433,
|
||||
0x2CFFE7D5, 0x6E0F063E,
|
||||
0xA91E2402, 0xEBEEC5E9,
|
||||
0x65CC8190, 0x273C607B,
|
||||
0xE02D4247, 0xA2DDA3AC,
|
||||
0xBE992B5F, 0xFC69CAB4,
|
||||
0x3B78E888, 0x79880963,
|
||||
0xF7AA4D1A, 0xB55AACF1,
|
||||
0x724B8ECD, 0x30BB6F26,
|
||||
0x4AC29F2A, 0x08327EC1,
|
||||
0xCF235CFD, 0x8DD3BD16,
|
||||
0x03F1F96F, 0x41011884,
|
||||
0x86103AB8, 0xC4E0DB53,
|
||||
0xD8A453A0, 0x9A54B24B,
|
||||
0x5D459077, 0x1FB5719C,
|
||||
0x919735E5, 0xD367D40E,
|
||||
0x1476F632, 0x568617D9,
|
||||
0xE085162A, 0xA275F7C1,
|
||||
0x6564D5FD, 0x27943416,
|
||||
0xA9B6706F, 0xEB469184,
|
||||
0x2C57B3B8, 0x6EA75253,
|
||||
0x72E3DAA0, 0x30133B4B,
|
||||
0xF7021977, 0xB5F2F89C,
|
||||
0x3BD0BCE5, 0x79205D0E,
|
||||
0xBE317F32, 0xFCC19ED9,
|
||||
0x86B86ED5, 0xC4488F3E,
|
||||
0x0359AD02, 0x41A94CE9,
|
||||
0xCF8B0890, 0x8D7BE97B,
|
||||
0x4A6ACB47, 0x089A2AAC,
|
||||
0x14DEA25F, 0x562E43B4,
|
||||
0x913F6188, 0xD3CF8063,
|
||||
0x5DEDC41A, 0x1F1D25F1,
|
||||
0xD80C07CD, 0x9AFCE626
|
||||
};
|
||||
#else /* int64 works */
|
||||
|
||||
const uint64 pg_crc64_table[256] = {
|
||||
UINT64CONST(0x0000000000000000), UINT64CONST(0x42F0E1EBA9EA3693),
|
||||
UINT64CONST(0x85E1C3D753D46D26), UINT64CONST(0xC711223CFA3E5BB5),
|
||||
UINT64CONST(0x493366450E42ECDF), UINT64CONST(0x0BC387AEA7A8DA4C),
|
||||
UINT64CONST(0xCCD2A5925D9681F9), UINT64CONST(0x8E224479F47CB76A),
|
||||
UINT64CONST(0x9266CC8A1C85D9BE), UINT64CONST(0xD0962D61B56FEF2D),
|
||||
UINT64CONST(0x17870F5D4F51B498), UINT64CONST(0x5577EEB6E6BB820B),
|
||||
UINT64CONST(0xDB55AACF12C73561), UINT64CONST(0x99A54B24BB2D03F2),
|
||||
UINT64CONST(0x5EB4691841135847), UINT64CONST(0x1C4488F3E8F96ED4),
|
||||
UINT64CONST(0x663D78FF90E185EF), UINT64CONST(0x24CD9914390BB37C),
|
||||
UINT64CONST(0xE3DCBB28C335E8C9), UINT64CONST(0xA12C5AC36ADFDE5A),
|
||||
UINT64CONST(0x2F0E1EBA9EA36930), UINT64CONST(0x6DFEFF5137495FA3),
|
||||
UINT64CONST(0xAAEFDD6DCD770416), UINT64CONST(0xE81F3C86649D3285),
|
||||
UINT64CONST(0xF45BB4758C645C51), UINT64CONST(0xB6AB559E258E6AC2),
|
||||
UINT64CONST(0x71BA77A2DFB03177), UINT64CONST(0x334A9649765A07E4),
|
||||
UINT64CONST(0xBD68D2308226B08E), UINT64CONST(0xFF9833DB2BCC861D),
|
||||
UINT64CONST(0x388911E7D1F2DDA8), UINT64CONST(0x7A79F00C7818EB3B),
|
||||
UINT64CONST(0xCC7AF1FF21C30BDE), UINT64CONST(0x8E8A101488293D4D),
|
||||
UINT64CONST(0x499B3228721766F8), UINT64CONST(0x0B6BD3C3DBFD506B),
|
||||
UINT64CONST(0x854997BA2F81E701), UINT64CONST(0xC7B97651866BD192),
|
||||
UINT64CONST(0x00A8546D7C558A27), UINT64CONST(0x4258B586D5BFBCB4),
|
||||
UINT64CONST(0x5E1C3D753D46D260), UINT64CONST(0x1CECDC9E94ACE4F3),
|
||||
UINT64CONST(0xDBFDFEA26E92BF46), UINT64CONST(0x990D1F49C77889D5),
|
||||
UINT64CONST(0x172F5B3033043EBF), UINT64CONST(0x55DFBADB9AEE082C),
|
||||
UINT64CONST(0x92CE98E760D05399), UINT64CONST(0xD03E790CC93A650A),
|
||||
UINT64CONST(0xAA478900B1228E31), UINT64CONST(0xE8B768EB18C8B8A2),
|
||||
UINT64CONST(0x2FA64AD7E2F6E317), UINT64CONST(0x6D56AB3C4B1CD584),
|
||||
UINT64CONST(0xE374EF45BF6062EE), UINT64CONST(0xA1840EAE168A547D),
|
||||
UINT64CONST(0x66952C92ECB40FC8), UINT64CONST(0x2465CD79455E395B),
|
||||
UINT64CONST(0x3821458AADA7578F), UINT64CONST(0x7AD1A461044D611C),
|
||||
UINT64CONST(0xBDC0865DFE733AA9), UINT64CONST(0xFF3067B657990C3A),
|
||||
UINT64CONST(0x711223CFA3E5BB50), UINT64CONST(0x33E2C2240A0F8DC3),
|
||||
UINT64CONST(0xF4F3E018F031D676), UINT64CONST(0xB60301F359DBE0E5),
|
||||
UINT64CONST(0xDA050215EA6C212F), UINT64CONST(0x98F5E3FE438617BC),
|
||||
UINT64CONST(0x5FE4C1C2B9B84C09), UINT64CONST(0x1D14202910527A9A),
|
||||
UINT64CONST(0x93366450E42ECDF0), UINT64CONST(0xD1C685BB4DC4FB63),
|
||||
UINT64CONST(0x16D7A787B7FAA0D6), UINT64CONST(0x5427466C1E109645),
|
||||
UINT64CONST(0x4863CE9FF6E9F891), UINT64CONST(0x0A932F745F03CE02),
|
||||
UINT64CONST(0xCD820D48A53D95B7), UINT64CONST(0x8F72ECA30CD7A324),
|
||||
UINT64CONST(0x0150A8DAF8AB144E), UINT64CONST(0x43A04931514122DD),
|
||||
UINT64CONST(0x84B16B0DAB7F7968), UINT64CONST(0xC6418AE602954FFB),
|
||||
UINT64CONST(0xBC387AEA7A8DA4C0), UINT64CONST(0xFEC89B01D3679253),
|
||||
UINT64CONST(0x39D9B93D2959C9E6), UINT64CONST(0x7B2958D680B3FF75),
|
||||
UINT64CONST(0xF50B1CAF74CF481F), UINT64CONST(0xB7FBFD44DD257E8C),
|
||||
UINT64CONST(0x70EADF78271B2539), UINT64CONST(0x321A3E938EF113AA),
|
||||
UINT64CONST(0x2E5EB66066087D7E), UINT64CONST(0x6CAE578BCFE24BED),
|
||||
UINT64CONST(0xABBF75B735DC1058), UINT64CONST(0xE94F945C9C3626CB),
|
||||
UINT64CONST(0x676DD025684A91A1), UINT64CONST(0x259D31CEC1A0A732),
|
||||
UINT64CONST(0xE28C13F23B9EFC87), UINT64CONST(0xA07CF2199274CA14),
|
||||
UINT64CONST(0x167FF3EACBAF2AF1), UINT64CONST(0x548F120162451C62),
|
||||
UINT64CONST(0x939E303D987B47D7), UINT64CONST(0xD16ED1D631917144),
|
||||
UINT64CONST(0x5F4C95AFC5EDC62E), UINT64CONST(0x1DBC74446C07F0BD),
|
||||
UINT64CONST(0xDAAD56789639AB08), UINT64CONST(0x985DB7933FD39D9B),
|
||||
UINT64CONST(0x84193F60D72AF34F), UINT64CONST(0xC6E9DE8B7EC0C5DC),
|
||||
UINT64CONST(0x01F8FCB784FE9E69), UINT64CONST(0x43081D5C2D14A8FA),
|
||||
UINT64CONST(0xCD2A5925D9681F90), UINT64CONST(0x8FDAB8CE70822903),
|
||||
UINT64CONST(0x48CB9AF28ABC72B6), UINT64CONST(0x0A3B7B1923564425),
|
||||
UINT64CONST(0x70428B155B4EAF1E), UINT64CONST(0x32B26AFEF2A4998D),
|
||||
UINT64CONST(0xF5A348C2089AC238), UINT64CONST(0xB753A929A170F4AB),
|
||||
UINT64CONST(0x3971ED50550C43C1), UINT64CONST(0x7B810CBBFCE67552),
|
||||
UINT64CONST(0xBC902E8706D82EE7), UINT64CONST(0xFE60CF6CAF321874),
|
||||
UINT64CONST(0xE224479F47CB76A0), UINT64CONST(0xA0D4A674EE214033),
|
||||
UINT64CONST(0x67C58448141F1B86), UINT64CONST(0x253565A3BDF52D15),
|
||||
UINT64CONST(0xAB1721DA49899A7F), UINT64CONST(0xE9E7C031E063ACEC),
|
||||
UINT64CONST(0x2EF6E20D1A5DF759), UINT64CONST(0x6C0603E6B3B7C1CA),
|
||||
UINT64CONST(0xF6FAE5C07D3274CD), UINT64CONST(0xB40A042BD4D8425E),
|
||||
UINT64CONST(0x731B26172EE619EB), UINT64CONST(0x31EBC7FC870C2F78),
|
||||
UINT64CONST(0xBFC9838573709812), UINT64CONST(0xFD39626EDA9AAE81),
|
||||
UINT64CONST(0x3A28405220A4F534), UINT64CONST(0x78D8A1B9894EC3A7),
|
||||
UINT64CONST(0x649C294A61B7AD73), UINT64CONST(0x266CC8A1C85D9BE0),
|
||||
UINT64CONST(0xE17DEA9D3263C055), UINT64CONST(0xA38D0B769B89F6C6),
|
||||
UINT64CONST(0x2DAF4F0F6FF541AC), UINT64CONST(0x6F5FAEE4C61F773F),
|
||||
UINT64CONST(0xA84E8CD83C212C8A), UINT64CONST(0xEABE6D3395CB1A19),
|
||||
UINT64CONST(0x90C79D3FEDD3F122), UINT64CONST(0xD2377CD44439C7B1),
|
||||
UINT64CONST(0x15265EE8BE079C04), UINT64CONST(0x57D6BF0317EDAA97),
|
||||
UINT64CONST(0xD9F4FB7AE3911DFD), UINT64CONST(0x9B041A914A7B2B6E),
|
||||
UINT64CONST(0x5C1538ADB04570DB), UINT64CONST(0x1EE5D94619AF4648),
|
||||
UINT64CONST(0x02A151B5F156289C), UINT64CONST(0x4051B05E58BC1E0F),
|
||||
UINT64CONST(0x87409262A28245BA), UINT64CONST(0xC5B073890B687329),
|
||||
UINT64CONST(0x4B9237F0FF14C443), UINT64CONST(0x0962D61B56FEF2D0),
|
||||
UINT64CONST(0xCE73F427ACC0A965), UINT64CONST(0x8C8315CC052A9FF6),
|
||||
UINT64CONST(0x3A80143F5CF17F13), UINT64CONST(0x7870F5D4F51B4980),
|
||||
UINT64CONST(0xBF61D7E80F251235), UINT64CONST(0xFD913603A6CF24A6),
|
||||
UINT64CONST(0x73B3727A52B393CC), UINT64CONST(0x31439391FB59A55F),
|
||||
UINT64CONST(0xF652B1AD0167FEEA), UINT64CONST(0xB4A25046A88DC879),
|
||||
UINT64CONST(0xA8E6D8B54074A6AD), UINT64CONST(0xEA16395EE99E903E),
|
||||
UINT64CONST(0x2D071B6213A0CB8B), UINT64CONST(0x6FF7FA89BA4AFD18),
|
||||
UINT64CONST(0xE1D5BEF04E364A72), UINT64CONST(0xA3255F1BE7DC7CE1),
|
||||
UINT64CONST(0x64347D271DE22754), UINT64CONST(0x26C49CCCB40811C7),
|
||||
UINT64CONST(0x5CBD6CC0CC10FAFC), UINT64CONST(0x1E4D8D2B65FACC6F),
|
||||
UINT64CONST(0xD95CAF179FC497DA), UINT64CONST(0x9BAC4EFC362EA149),
|
||||
UINT64CONST(0x158E0A85C2521623), UINT64CONST(0x577EEB6E6BB820B0),
|
||||
UINT64CONST(0x906FC95291867B05), UINT64CONST(0xD29F28B9386C4D96),
|
||||
UINT64CONST(0xCEDBA04AD0952342), UINT64CONST(0x8C2B41A1797F15D1),
|
||||
UINT64CONST(0x4B3A639D83414E64), UINT64CONST(0x09CA82762AAB78F7),
|
||||
UINT64CONST(0x87E8C60FDED7CF9D), UINT64CONST(0xC51827E4773DF90E),
|
||||
UINT64CONST(0x020905D88D03A2BB), UINT64CONST(0x40F9E43324E99428),
|
||||
UINT64CONST(0x2CFFE7D5975E55E2), UINT64CONST(0x6E0F063E3EB46371),
|
||||
UINT64CONST(0xA91E2402C48A38C4), UINT64CONST(0xEBEEC5E96D600E57),
|
||||
UINT64CONST(0x65CC8190991CB93D), UINT64CONST(0x273C607B30F68FAE),
|
||||
UINT64CONST(0xE02D4247CAC8D41B), UINT64CONST(0xA2DDA3AC6322E288),
|
||||
UINT64CONST(0xBE992B5F8BDB8C5C), UINT64CONST(0xFC69CAB42231BACF),
|
||||
UINT64CONST(0x3B78E888D80FE17A), UINT64CONST(0x7988096371E5D7E9),
|
||||
UINT64CONST(0xF7AA4D1A85996083), UINT64CONST(0xB55AACF12C735610),
|
||||
UINT64CONST(0x724B8ECDD64D0DA5), UINT64CONST(0x30BB6F267FA73B36),
|
||||
UINT64CONST(0x4AC29F2A07BFD00D), UINT64CONST(0x08327EC1AE55E69E),
|
||||
UINT64CONST(0xCF235CFD546BBD2B), UINT64CONST(0x8DD3BD16FD818BB8),
|
||||
UINT64CONST(0x03F1F96F09FD3CD2), UINT64CONST(0x41011884A0170A41),
|
||||
UINT64CONST(0x86103AB85A2951F4), UINT64CONST(0xC4E0DB53F3C36767),
|
||||
UINT64CONST(0xD8A453A01B3A09B3), UINT64CONST(0x9A54B24BB2D03F20),
|
||||
UINT64CONST(0x5D45907748EE6495), UINT64CONST(0x1FB5719CE1045206),
|
||||
UINT64CONST(0x919735E51578E56C), UINT64CONST(0xD367D40EBC92D3FF),
|
||||
UINT64CONST(0x1476F63246AC884A), UINT64CONST(0x568617D9EF46BED9),
|
||||
UINT64CONST(0xE085162AB69D5E3C), UINT64CONST(0xA275F7C11F7768AF),
|
||||
UINT64CONST(0x6564D5FDE549331A), UINT64CONST(0x279434164CA30589),
|
||||
UINT64CONST(0xA9B6706FB8DFB2E3), UINT64CONST(0xEB46918411358470),
|
||||
UINT64CONST(0x2C57B3B8EB0BDFC5), UINT64CONST(0x6EA7525342E1E956),
|
||||
UINT64CONST(0x72E3DAA0AA188782), UINT64CONST(0x30133B4B03F2B111),
|
||||
UINT64CONST(0xF7021977F9CCEAA4), UINT64CONST(0xB5F2F89C5026DC37),
|
||||
UINT64CONST(0x3BD0BCE5A45A6B5D), UINT64CONST(0x79205D0E0DB05DCE),
|
||||
UINT64CONST(0xBE317F32F78E067B), UINT64CONST(0xFCC19ED95E6430E8),
|
||||
UINT64CONST(0x86B86ED5267CDBD3), UINT64CONST(0xC4488F3E8F96ED40),
|
||||
UINT64CONST(0x0359AD0275A8B6F5), UINT64CONST(0x41A94CE9DC428066),
|
||||
UINT64CONST(0xCF8B0890283E370C), UINT64CONST(0x8D7BE97B81D4019F),
|
||||
UINT64CONST(0x4A6ACB477BEA5A2A), UINT64CONST(0x089A2AACD2006CB9),
|
||||
UINT64CONST(0x14DEA25F3AF9026D), UINT64CONST(0x562E43B4931334FE),
|
||||
UINT64CONST(0x913F6188692D6F4B), UINT64CONST(0xD3CF8063C0C759D8),
|
||||
UINT64CONST(0x5DEDC41A34BBEEB2), UINT64CONST(0x1F1D25F19D51D821),
|
||||
UINT64CONST(0xD80C07CD676F8394), UINT64CONST(0x9AFCE626CE85B507)
|
||||
};
|
||||
#endif /* INT64_IS_BUSTED */
|
||||
|
||||
#endif /* PROVIDE_64BIT_CRC */
|
105
pgsql_src/pg_ctl.c
Normal file
105
pgsql_src/pg_ctl.c
Normal file
@ -0,0 +1,105 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pg_ctl --- start/stops/restarts the PostgreSQL server
|
||||
*
|
||||
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.111 2009/06/11 14:49:07 momjian Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
#include "postgres_fe.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
/* PID can be negative for standalone backend */
|
||||
typedef long pgpid_t;
|
||||
|
||||
static pgpid_t get_pgpid(void);
|
||||
static bool postmaster_is_alive(pid_t pid);
|
||||
|
||||
static char pid_file[MAXPGPATH];
|
||||
|
||||
|
||||
static pgpid_t
|
||||
get_pgpid(void)
|
||||
{
|
||||
FILE *pidf;
|
||||
long pid;
|
||||
|
||||
snprintf(pid_file, lengthof(pid_file), "%s/postmaster.pid", pgdata);
|
||||
pidf = fopen(pid_file, "r");
|
||||
if (pidf == NULL)
|
||||
{
|
||||
/* No pid file, not an error on startup */
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
else
|
||||
elog(ERROR_SYSTEM, _("could not open PID file \"%s\": %s\n"),
|
||||
pid_file, strerror(errno));
|
||||
}
|
||||
if (fscanf(pidf, "%ld", &pid) != 1)
|
||||
elog(ERROR_PID_BROKEN, _("invalid data in PID file \"%s\"\n"), pid_file);
|
||||
fclose(pidf);
|
||||
return (pgpid_t) pid;
|
||||
}
|
||||
|
||||
/*
|
||||
* utility routines
|
||||
*/
|
||||
|
||||
static bool
|
||||
postmaster_is_alive(pid_t pid)
|
||||
{
|
||||
/*
|
||||
* Test to see if the process is still there. Note that we do not
|
||||
* consider an EPERM failure to mean that the process is still there;
|
||||
* EPERM must mean that the given PID belongs to some other userid, and
|
||||
* considering the permissions on $PGDATA, that means it's not the
|
||||
* postmaster we are after.
|
||||
*
|
||||
* Don't believe that our own PID or parent shell's PID is the postmaster,
|
||||
* either. (Windows hasn't got getppid(), though.)
|
||||
*/
|
||||
if (pid == getpid())
|
||||
return false;
|
||||
#ifndef WIN32
|
||||
if (pid == getppid())
|
||||
return false;
|
||||
#endif
|
||||
if (kill(pid, 0) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* original is do_status() in src/bin/pg_ctl/pg_ctl.c
|
||||
* changes are:
|
||||
* renamed from do_status() from do_status().
|
||||
* return true if PG server is running.
|
||||
* don't print any message.
|
||||
* don't print postopts file.
|
||||
* log with elog() in pgut library.
|
||||
*/
|
||||
bool
|
||||
is_pg_running(void)
|
||||
{
|
||||
pgpid_t pid;
|
||||
|
||||
pid = get_pgpid();
|
||||
if (pid == 0) /* 0 means no pid file */
|
||||
return false;
|
||||
|
||||
if (pid < 0) /* standalone backend */
|
||||
pid = -pid;
|
||||
|
||||
|
||||
return postmaster_is_alive((pid_t) pid);
|
||||
}
|
||||
|
240
pgut/pgut-port.c
Normal file
240
pgut/pgut-port.c
Normal file
@ -0,0 +1,240 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pgut-port.c
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "c.h"
|
||||
#include "pgut-port.h"
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#include <winioctl.h>
|
||||
|
||||
int
|
||||
uname(struct utsname *buf)
|
||||
{
|
||||
OSVERSIONINFO os = { sizeof(OSVERSIONINFO) };
|
||||
SYSTEM_INFO sys = { { sizeof(SYSTEM_INFO) } };
|
||||
DWORD bufsize;
|
||||
|
||||
GetVersionEx(&os);
|
||||
GetSystemInfo(&sys);
|
||||
|
||||
/* sysname */
|
||||
strcpy(buf->sysname, "Windows");
|
||||
|
||||
/* nodename */
|
||||
bufsize = lengthof(buf->nodename);
|
||||
GetComputerName(buf->nodename, &bufsize);
|
||||
|
||||
/* release: major.minor */
|
||||
snprintf(buf->release, lengthof(buf->release), "%ld.%ld",
|
||||
os.dwMajorVersion, os.dwMinorVersion);
|
||||
|
||||
/* version */
|
||||
strcpy(buf->sysname, os.szCSDVersion);
|
||||
|
||||
/* machine */
|
||||
switch (sys.wProcessorArchitecture)
|
||||
{
|
||||
case PROCESSOR_ARCHITECTURE_INTEL:
|
||||
strcpy(buf->machine, "x86");
|
||||
break;
|
||||
case PROCESSOR_ARCHITECTURE_IA64:
|
||||
strcpy(buf->machine, "IA64");
|
||||
break;
|
||||
case PROCESSOR_ARCHITECTURE_AMD64:
|
||||
strcpy(buf->machine, "x86_64");
|
||||
break;
|
||||
case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
|
||||
strcpy(buf->machine, "x86_on_win64");
|
||||
break;
|
||||
default:
|
||||
strcpy(buf->machine, "unknown");
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define NTFS_BLOCK_SIZE 512
|
||||
|
||||
int
|
||||
statfs(const char *path, struct statfs *buf)
|
||||
{
|
||||
ULARGE_INTEGER availBytes;
|
||||
ULARGE_INTEGER totalBytes;
|
||||
ULARGE_INTEGER freeBytes;
|
||||
|
||||
if (!GetDiskFreeSpaceEx(path, &availBytes, &totalBytes, &freeBytes))
|
||||
{
|
||||
_dosmaperr(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(buf, 0, sizeof(struct statfs));
|
||||
buf->f_type = NTFS_SB_MAGIC;
|
||||
buf->f_bsize = NTFS_BLOCK_SIZE;
|
||||
buf->f_blocks = (long) (totalBytes.QuadPart / NTFS_BLOCK_SIZE);
|
||||
buf->f_bfree = (long) (freeBytes.QuadPart / NTFS_BLOCK_SIZE);
|
||||
buf->f_bavail = (long) (availBytes.QuadPart / NTFS_BLOCK_SIZE);
|
||||
buf->f_namelen = MAX_PATH;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define REPARSE_DATA_SIZE 1024
|
||||
|
||||
/* same layout as REPARSE_DATA_BUFFER, which is defined only in old winnt.h */
|
||||
typedef struct REPARSE_DATA
|
||||
{
|
||||
ULONG ReparseTag;
|
||||
WORD ReparseDataLength;
|
||||
WORD Reserved;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
WORD SubstituteNameOffset;
|
||||
WORD SubstituteNameLength;
|
||||
WORD PrintNameOffset;
|
||||
WORD PrintNameLength;
|
||||
ULONG Flags;
|
||||
WCHAR PathBuffer[1];
|
||||
} Symlink;
|
||||
struct
|
||||
{
|
||||
WORD SubstituteNameOffset;
|
||||
WORD SubstituteNameLength;
|
||||
WORD PrintNameOffset;
|
||||
WORD PrintNameLength;
|
||||
WCHAR PathBuffer[1];
|
||||
} Mount;
|
||||
struct
|
||||
{
|
||||
BYTE DataBuffer[REPARSE_DATA_SIZE];
|
||||
} Generic;
|
||||
};
|
||||
} REPARSE_DATA;
|
||||
|
||||
ssize_t
|
||||
readlink(const char *path, char *target, size_t size)
|
||||
{
|
||||
HANDLE handle;
|
||||
DWORD attr;
|
||||
REPARSE_DATA data;
|
||||
DWORD datasize;
|
||||
PCWSTR wpath;
|
||||
int wlen;
|
||||
int r;
|
||||
|
||||
attr = GetFileAttributes(path);
|
||||
if (attr == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
_dosmaperr(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
|
||||
{
|
||||
errno = EINVAL; /* not a symlink */
|
||||
return -1;
|
||||
}
|
||||
|
||||
handle = CreateFileA(path, 0,
|
||||
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
_dosmaperr(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
wpath = NULL;
|
||||
if (DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
|
||||
&data, sizeof(data), &datasize, NULL))
|
||||
{
|
||||
switch (data.ReparseTag)
|
||||
{
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
{
|
||||
wpath = data.Mount.PathBuffer + data.Mount.SubstituteNameOffset;
|
||||
wlen = data.Mount.SubstituteNameLength;
|
||||
break;
|
||||
}
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
{
|
||||
wpath = data.Symlink.PathBuffer + data.Symlink.SubstituteNameOffset;
|
||||
wlen = data.Symlink.SubstituteNameLength;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wpath == NULL)
|
||||
r = -1;
|
||||
else
|
||||
{
|
||||
if (wcsncmp(wpath, L"\\??\\", 4) == 0 ||
|
||||
wcsncmp(wpath, L"\\\\?\\", 4) == 0)
|
||||
{
|
||||
wpath += 4;
|
||||
wlen -= 4;
|
||||
}
|
||||
r = WideCharToMultiByte(CP_ACP, 0, wpath, wlen, target, size, NULL, NULL);
|
||||
}
|
||||
|
||||
CloseHandle(handle);
|
||||
return r;
|
||||
}
|
||||
|
||||
char *
|
||||
blkid_devno_to_devname(dev_t devno)
|
||||
{
|
||||
static char devname[4];
|
||||
char letter = 'A' + devno;
|
||||
if ('A' <= letter && letter <= 'Z')
|
||||
{
|
||||
snprintf(devname, lengthof(devname), "%c:\\", 'A' + devno);
|
||||
return devname;
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
flock(int fd, int operation)
|
||||
{
|
||||
BOOL ret;
|
||||
HANDLE handle = (HANDLE) _get_osfhandle(fd);
|
||||
DWORD lo = 0;
|
||||
DWORD hi = 1;
|
||||
|
||||
if (operation & LOCK_UN)
|
||||
{
|
||||
ret = UnlockFileEx(handle, 0, lo, hi, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD flags = 0;
|
||||
if (operation & LOCK_EX)
|
||||
flags |= LOCKFILE_EXCLUSIVE_LOCK;
|
||||
if (operation & LOCK_NB)
|
||||
flags |= LOCKFILE_FAIL_IMMEDIATELY;
|
||||
ret = LockFileEx(handle, flags, 0, lo, hi, NULL);
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
_dosmaperr(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
71
pgut/pgut-port.h
Normal file
71
pgut/pgut-port.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pgut-port.h
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef PGUT_PORT_H
|
||||
#define PGUT_PORT_H
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
#include <blkid/blkid.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <time.h>
|
||||
|
||||
struct utsname
|
||||
{
|
||||
char sysname[32];
|
||||
char nodename[256];
|
||||
char release[128];
|
||||
char version[128];
|
||||
char machine[32];
|
||||
};
|
||||
|
||||
#define NTFS_SB_MAGIC 0x5346544e
|
||||
|
||||
typedef struct { int val[2]; } fsid_t;
|
||||
|
||||
struct statfs
|
||||
{
|
||||
long f_type;
|
||||
long f_bsize;
|
||||
long f_blocks;
|
||||
long f_bfree;
|
||||
long f_bavail;
|
||||
long f_files;
|
||||
long f_ffree;
|
||||
fsid_t f_fsid;
|
||||
long f_namelen;
|
||||
};
|
||||
|
||||
extern int uname(struct utsname *buf);
|
||||
extern int statfs(const char *path, struct statfs *buf);
|
||||
extern ssize_t readlink(const char *path, char *target, size_t size);
|
||||
extern char *blkid_devno_to_devname(dev_t devno);
|
||||
|
||||
#define LOCK_SH 1 /* Shared lock. */
|
||||
#define LOCK_EX 2 /* Exclusive lock. */
|
||||
#define LOCK_UN 8 /* Unlock. */
|
||||
#define LOCK_NB 4 /* Don't block when locking. */
|
||||
|
||||
extern int flock(int fd, int operation);
|
||||
|
||||
#define S_IFLNK (0)
|
||||
#define S_IRWXG (0)
|
||||
#define S_IRWXO (0)
|
||||
#define S_ISLNK(mode) (0)
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* PGUT_PORT_H */
|
1686
pgut/pgut.c
Normal file
1686
pgut/pgut.c
Normal file
File diff suppressed because it is too large
Load Diff
245
pgut/pgut.h
Normal file
245
pgut/pgut.h
Normal file
@ -0,0 +1,245 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* pgut.h
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef PGUT_H
|
||||
#define PGUT_H
|
||||
|
||||
#include "libpq-fe.h"
|
||||
#include "pqexpbuffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#if !defined(C_H) && !defined(__cplusplus)
|
||||
#ifndef bool
|
||||
typedef char bool;
|
||||
#endif
|
||||
#ifndef true
|
||||
#define true ((bool) 1)
|
||||
#endif
|
||||
#ifndef false
|
||||
#define false ((bool) 0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define INFINITE_STR "INFINITE"
|
||||
|
||||
typedef enum YesNo
|
||||
{
|
||||
DEFAULT,
|
||||
NO,
|
||||
YES
|
||||
} YesNo;
|
||||
|
||||
typedef enum pgut_optsrc
|
||||
{
|
||||
SOURCE_DEFAULT,
|
||||
SOURCE_ENV,
|
||||
SOURCE_FILE,
|
||||
SOURCE_CMDLINE,
|
||||
SOURCE_CONST
|
||||
} pgut_optsrc;
|
||||
|
||||
/*
|
||||
* type:
|
||||
* b: bool (true)
|
||||
* B: bool (false)
|
||||
* f: pgut_optfn
|
||||
* i: 32bit singed integer
|
||||
* u: 32bit unsigned integer
|
||||
* I: 64bit singed integer
|
||||
* U: 64bit unsigned integer
|
||||
* s: string
|
||||
* t: time_t
|
||||
* y: YesNo (YES)
|
||||
* Y: YesNo (NO)
|
||||
*/
|
||||
typedef struct pgut_option
|
||||
{
|
||||
char type;
|
||||
char sname; /* short name */
|
||||
const char *lname; /* long name */
|
||||
void *var; /* pointer to variable */
|
||||
pgut_optsrc allowed; /* allowed source */
|
||||
pgut_optsrc source; /* actual source */
|
||||
} pgut_option;
|
||||
|
||||
typedef void (*pgut_optfn) (pgut_option *opt, const char *arg);
|
||||
typedef void (*pgut_atexit_callback)(bool fatal, void *userdata);
|
||||
|
||||
/*
|
||||
* pgut client variables and functions
|
||||
*/
|
||||
extern const char *PROGRAM_NAME;
|
||||
extern const char *PROGRAM_VERSION;
|
||||
extern const char *PROGRAM_URL;
|
||||
extern const char *PROGRAM_EMAIL;
|
||||
|
||||
extern void pgut_help(bool details);
|
||||
|
||||
/*
|
||||
* pgut framework variables and functions
|
||||
*/
|
||||
extern const char *dbname;
|
||||
extern const char *host;
|
||||
extern const char *port;
|
||||
extern const char *username;
|
||||
extern char *password;
|
||||
extern bool debug;
|
||||
extern bool quiet;
|
||||
|
||||
#ifndef PGUT_NO_PROMPT
|
||||
extern YesNo prompt_password;
|
||||
#endif
|
||||
|
||||
extern PGconn *connection;
|
||||
extern bool interrupted;
|
||||
|
||||
extern void help(bool details);
|
||||
extern int pgut_getopt(int argc, char **argv, pgut_option options[]);
|
||||
extern void pgut_readopt(const char *path, pgut_option options[], int elevel);
|
||||
extern void pgut_atexit_push(pgut_atexit_callback callback, void *userdata);
|
||||
extern void pgut_atexit_pop(pgut_atexit_callback callback, void *userdata);
|
||||
|
||||
/*
|
||||
* Database connections
|
||||
*/
|
||||
extern PGconn *pgut_connect(int elevel);
|
||||
extern void pgut_disconnect(PGconn *conn);
|
||||
extern PGresult *pgut_execute(PGconn* conn, const char *query, int nParams, const char **params, int elevel);
|
||||
extern void pgut_command(PGconn* conn, const char *query, int nParams, const char **params, int elevel);
|
||||
extern bool pgut_send(PGconn* conn, const char *query, int nParams, const char **params, int elevel);
|
||||
extern int pgut_wait(int num, PGconn *connections[], struct timeval *timeout);
|
||||
|
||||
extern PGconn *reconnect_elevel(int elevel);
|
||||
extern void reconnect(void);
|
||||
extern void disconnect(void);
|
||||
extern PGresult *execute_elevel(const char *query, int nParams, const char **params, int elevel);
|
||||
extern PGresult *execute(const char *query, int nParams, const char **params);
|
||||
extern void command(const char *query, int nParams, const char **params);
|
||||
|
||||
/*
|
||||
* memory allocators
|
||||
*/
|
||||
extern void *pgut_malloc(size_t size);
|
||||
extern void *pgut_realloc(void *p, size_t size);
|
||||
extern char *pgut_strdup(const char *str);
|
||||
extern char *strdup_with_len(const char *str, size_t len);
|
||||
extern char *strdup_trim(const char *str);
|
||||
|
||||
/*
|
||||
* elog
|
||||
*/
|
||||
#define LOG (-4)
|
||||
#define INFO (-3)
|
||||
#define NOTICE (-2)
|
||||
#define WARNING (-1)
|
||||
#define HELP 1
|
||||
#define ERROR 2
|
||||
#define FATAL 3
|
||||
#define PANIC 4
|
||||
|
||||
#define ERROR_SYSTEM 10 /* I/O or system error */
|
||||
#define ERROR_NOMEM 11 /* memory exhausted */
|
||||
#define ERROR_ARGS 12 /* some configurations are invalid */
|
||||
#define ERROR_INTERRUPTED 13 /* interrupted by signal */
|
||||
#define ERROR_PG_COMMAND 14 /* PostgreSQL query or command error */
|
||||
#define ERROR_PG_CONNECT 15 /* PostgreSQL connection error */
|
||||
|
||||
#undef elog
|
||||
extern void
|
||||
elog(int elevel, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
|
||||
/*
|
||||
* Assert
|
||||
*/
|
||||
#undef Assert
|
||||
#undef AssertArg
|
||||
#undef AssertMacro
|
||||
|
||||
#ifdef USE_ASSERT_CHECKING
|
||||
#define Assert(x) assert(x)
|
||||
#define AssertArg(x) assert(x)
|
||||
#define AssertMacro(x) assert(x)
|
||||
#else
|
||||
#define Assert(x) ((void) 0)
|
||||
#define AssertArg(x) ((void) 0)
|
||||
#define AssertMacro(x) ((void) 0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* StringInfo and string operations
|
||||
*/
|
||||
#define STRINGINFO_H
|
||||
|
||||
#define StringInfoData PQExpBufferData
|
||||
#define StringInfo PQExpBuffer
|
||||
#define makeStringInfo createPQExpBuffer
|
||||
#define initStringInfo initPQExpBuffer
|
||||
#define freeStringInfo destroyPQExpBuffer
|
||||
#define termStringInfo termPQExpBuffer
|
||||
#define resetStringInfo resetPQExpBuffer
|
||||
#define enlargeStringInfo enlargePQExpBuffer
|
||||
#define printfStringInfo printfPQExpBuffer /* reset + append */
|
||||
#define appendStringInfo appendPQExpBuffer
|
||||
#define appendStringInfoString appendPQExpBufferStr
|
||||
#define appendStringInfoChar appendPQExpBufferChar
|
||||
#define appendBinaryStringInfo appendBinaryPQExpBuffer
|
||||
|
||||
extern int appendStringInfoFile(StringInfo str, FILE *fp);
|
||||
extern int appendStringInfoFd(StringInfo str, int fd);
|
||||
|
||||
extern bool parse_bool(const char *value, bool *result);
|
||||
extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
|
||||
extern bool parse_int32(const char *value, int32 *result);
|
||||
extern bool parse_uint32(const char *value, uint32 *result);
|
||||
extern bool parse_int64(const char *value, int64 *result);
|
||||
extern bool parse_uint64(const char *value, uint64 *result);
|
||||
extern bool parse_time(const char *value, time_t *time);
|
||||
|
||||
#define IsSpace(c) (isspace((unsigned char)(c)))
|
||||
#define IsAlpha(c) (isalpha((unsigned char)(c)))
|
||||
#define IsAlnum(c) (isalnum((unsigned char)(c)))
|
||||
#define IsIdentHead(c) (IsAlpha(c) || (c) == '_')
|
||||
#define IsIdentBody(c) (IsAlnum(c) || (c) == '_')
|
||||
#define ToLower(c) (tolower((unsigned char)(c)))
|
||||
#define ToUpper(c) (toupper((unsigned char)(c)))
|
||||
|
||||
/*
|
||||
* socket operations
|
||||
*/
|
||||
extern int wait_for_socket(int sock, struct timeval *timeout);
|
||||
extern int wait_for_sockets(int nfds, fd_set *fds, struct timeval *timeout);
|
||||
|
||||
/*
|
||||
* import from postgres.h and catalog/genbki.h in 8.4
|
||||
*/
|
||||
#if PG_VERSION_NUM < 80400
|
||||
|
||||
typedef unsigned long Datum;
|
||||
typedef struct MemoryContextData *MemoryContext;
|
||||
|
||||
#define CATALOG(name,oid) typedef struct CppConcat(FormData_,name)
|
||||
#define BKI_BOOTSTRAP
|
||||
#define BKI_SHARED_RELATION
|
||||
#define BKI_WITHOUT_OIDS
|
||||
#define DATA(x) extern int no_such_variable
|
||||
#define DESCR(x) extern int no_such_variable
|
||||
#define SHDESCR(x) extern int no_such_variable
|
||||
typedef int aclitem;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
extern int sleep(unsigned int seconds);
|
||||
extern int usleep(unsigned int usec);
|
||||
#endif
|
||||
|
||||
#endif /* PGUT_H */
|
978
restore.c
Normal file
978
restore.c
Normal file
@ -0,0 +1,978 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* restore.c: restore DB cluster and archived WAL.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "catalog/pg_control.h"
|
||||
|
||||
#if PG_VERSION_NUM < 80200
|
||||
#define XLOG_BLCKSZ BLCKSZ
|
||||
#endif
|
||||
|
||||
static void backup_online_files(bool re_recovery);
|
||||
static void restore_online_files(void);
|
||||
static void restore_database(pgBackup *backup);
|
||||
static void restore_archive_logs(pgBackup *backup);
|
||||
static void create_recovery_conf(const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli);
|
||||
static parray * readTimeLineHistory(TimeLineID targetTLI);
|
||||
static bool satisfy_timeline(const parray *timelines, const pgBackup *backup);
|
||||
static TimeLineID get_current_timeline(void);
|
||||
static TimeLineID get_fullbackup_timeline(parray *backups);
|
||||
static void print_backup_id(const pgBackup *backup);
|
||||
static void search_next_wal(const char *path, uint32 *needId, uint32 *needSeg, parray *timelines);
|
||||
|
||||
int
|
||||
do_restore(const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli)
|
||||
{
|
||||
int i;
|
||||
int base_index; /* index of base (full) backup */
|
||||
int last_restored_index; /* index of last restored database backup */
|
||||
int ret;
|
||||
TimeLineID cur_tli;
|
||||
TimeLineID backup_tli;
|
||||
parray *backups;
|
||||
pgBackup *base_backup = NULL;
|
||||
parray *files;
|
||||
parray *timelines;
|
||||
char timeline_dir[MAXPGPATH];
|
||||
uint32 needId = 0;
|
||||
uint32 needSeg = 0;
|
||||
|
||||
/* PGDATA and ARCLOG_PATH are always required */
|
||||
if (pgdata == NULL)
|
||||
elog(ERROR_ARGS,
|
||||
_("required parameter not specified: PGDATA (-D, --pgdata)"));
|
||||
if (arclog_path == NULL)
|
||||
elog(ERROR_ARGS,
|
||||
_("required parameter not specified: ARCLOG_PATH (-A, --arclog-path)"));
|
||||
if (srvlog_path == NULL)
|
||||
elog(ERROR_ARGS,
|
||||
_("required parameter not specified: SRVLOG_PATH (-S, --srvlog-path)"));
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
printf(_("========================================\n"));
|
||||
printf(_("restore start\n"));
|
||||
}
|
||||
|
||||
/* get exclusive lock of backup catalog */
|
||||
ret = catalog_lock();
|
||||
if (ret == -1)
|
||||
elog(ERROR_SYSTEM, _("can't lock backup catalog."));
|
||||
else if (ret == 1)
|
||||
elog(ERROR_ALREADY_RUNNING,
|
||||
_("another pg_rman is running, stop restore."));
|
||||
|
||||
/* confirm the PostgreSQL server is not running */
|
||||
if (is_pg_running())
|
||||
elog(ERROR_PG_RUNNING, _("PostgreSQL server is running"));
|
||||
|
||||
/* get list of backups. (index == 0) is the last backup */
|
||||
backups = catalog_get_backup_list(NULL);
|
||||
|
||||
cur_tli = get_current_timeline();
|
||||
backup_tli = get_fullbackup_timeline(backups);
|
||||
|
||||
/* determine target timeline */
|
||||
if (target_tli == 0)
|
||||
target_tli = cur_tli != 0 ? cur_tli : backup_tli;
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
printf(_("current timeline ID = %u\n"), cur_tli);
|
||||
printf(_("latest full backup timeline ID = %u\n"), backup_tli);
|
||||
printf(_("target timeline ID = %u\n"), target_tli);
|
||||
}
|
||||
|
||||
/* backup online WAL and serverlog */
|
||||
backup_online_files(cur_tli != 0 && cur_tli != backup_tli);
|
||||
|
||||
/*
|
||||
* Clear restore destination, but don't remove $PGDATA.
|
||||
* To remove symbolic link, get file list with "omit_symlink = false".
|
||||
*/
|
||||
if (!check)
|
||||
{
|
||||
if (verbose)
|
||||
{
|
||||
printf(_("----------------------------------------\n"));
|
||||
printf(_("clearing restore destination\n"));
|
||||
}
|
||||
files = parray_new();
|
||||
dir_list_file(files, pgdata, NULL, false, false);
|
||||
parray_qsort(files, pgFileComparePathDesc); /* delete from leaf */
|
||||
|
||||
for (i = 0; i < parray_num(files); i++)
|
||||
{
|
||||
pgFile *file = (pgFile *) parray_get(files, i);
|
||||
pgFileDelete(file);
|
||||
}
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
}
|
||||
|
||||
/*
|
||||
* restore timeline history files and get timeline branches can reach
|
||||
* recovery target point.
|
||||
*/
|
||||
snprintf(timeline_dir, lengthof(timeline_dir), "%s/%s", backup_path,
|
||||
TIMELINE_HISTORY_DIR);
|
||||
if (verbose && !check)
|
||||
printf(_("restoring timeline history files\n"));
|
||||
dir_copy_files(timeline_dir, arclog_path);
|
||||
timelines = readTimeLineHistory(target_tli);
|
||||
|
||||
/* find last full backup which can be used as base backup. */
|
||||
if (verbose)
|
||||
printf(_("searching recent full backup\n"));
|
||||
for (i = 0; i < parray_num(backups); i++)
|
||||
{
|
||||
base_backup = (pgBackup *) parray_get(backups, i);
|
||||
|
||||
if (base_backup->backup_mode < BACKUP_MODE_FULL ||
|
||||
base_backup->status != BACKUP_STATUS_OK)
|
||||
continue;
|
||||
|
||||
#ifndef HAVE_LIBZ
|
||||
/* Make sure we won't need decompression we haven't got */
|
||||
if (base_backup->compress_data &&
|
||||
(HAVE_DATABASE(base_backup) || HAVE_ARCLOG(base_backup)))
|
||||
{
|
||||
elog(EXIT_NOT_SUPPORTED,
|
||||
_("can't restore from compressed backup (compression not supported in this installation)"));
|
||||
}
|
||||
#endif
|
||||
if (satisfy_timeline(timelines, base_backup))
|
||||
{
|
||||
goto base_backup_found;
|
||||
}
|
||||
}
|
||||
/* no full backup found, can't restore */
|
||||
elog(ERROR_NO_BACKUP, _("no full backup found, can't restore."));
|
||||
|
||||
base_backup_found:
|
||||
base_index = i;
|
||||
|
||||
if (verbose)
|
||||
print_backup_id(base_backup);
|
||||
|
||||
/* restore base backup */
|
||||
restore_database(base_backup);
|
||||
last_restored_index = base_index;
|
||||
|
||||
/* restore following incremental backup */
|
||||
if (verbose)
|
||||
printf(_("searching incremental backup...\n"));
|
||||
for (i = base_index - 1; i >= 0; i--)
|
||||
{
|
||||
pgBackup *backup = (pgBackup *) parray_get(backups, i);
|
||||
|
||||
/* don't use incomplete nor different timeline backup */
|
||||
if (backup->status != BACKUP_STATUS_OK ||
|
||||
backup->tli != base_backup->tli)
|
||||
continue;
|
||||
|
||||
/* use database backup only */
|
||||
if (backup->backup_mode < BACKUP_MODE_INCREMENTAL)
|
||||
continue;
|
||||
|
||||
/* is the backup is necessary for restore to target timeline ? */
|
||||
if (!satisfy_timeline(timelines, backup))
|
||||
continue;
|
||||
|
||||
if (verbose)
|
||||
print_backup_id(backup);
|
||||
|
||||
restore_database(backup);
|
||||
last_restored_index = i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Restore archived WAL which backed up with or after last restored backup.
|
||||
* We don't check the backup->tli because a backup of arhived WAL
|
||||
* can contain WALs which were archived in multiple timeline.
|
||||
*/
|
||||
if (verbose)
|
||||
printf(_("searching backed-up WAL...\n"));
|
||||
|
||||
if (check)
|
||||
{
|
||||
pgBackup *backup = (pgBackup *) parray_get(backups, last_restored_index);
|
||||
/* XLByteToSeg(xlrp, logId, logSeg) */
|
||||
needId = backup->start_lsn.xlogid;
|
||||
needSeg = backup->start_lsn.xrecoff / XLogSegSize;
|
||||
}
|
||||
|
||||
for (i = last_restored_index; i >= 0; i--)
|
||||
{
|
||||
pgBackup *backup = (pgBackup *) parray_get(backups, i);
|
||||
|
||||
/* don't use incomplete backup */
|
||||
if (backup->status != BACKUP_STATUS_OK)
|
||||
continue;
|
||||
|
||||
if (!HAVE_ARCLOG(backup))
|
||||
continue;
|
||||
|
||||
/* care timeline junction */
|
||||
if (!satisfy_timeline(timelines, backup))
|
||||
continue;
|
||||
|
||||
restore_archive_logs(backup);
|
||||
|
||||
if (check)
|
||||
{
|
||||
char xlogpath[MAXPGPATH];
|
||||
|
||||
pgBackupGetPath(backup, xlogpath, lengthof(xlogpath), ARCLOG_DIR);
|
||||
search_next_wal(xlogpath, &needId, &needSeg, timelines);
|
||||
}
|
||||
}
|
||||
|
||||
/* copy online WAL backup to $PGDATA/pg_xlog */
|
||||
restore_online_files();
|
||||
|
||||
if (check)
|
||||
{
|
||||
char xlogpath[MAXPGPATH];
|
||||
if (verbose)
|
||||
printf(_("searching archived WAL...\n"));
|
||||
|
||||
search_next_wal(arclog_path, &needId, &needSeg, timelines);
|
||||
|
||||
if (verbose)
|
||||
printf(_("searching online WAL...\n"));
|
||||
|
||||
snprintf(xlogpath, lengthof(xlogpath), "%s/%s", pgdata, PG_XLOG_DIR);
|
||||
search_next_wal(xlogpath, &needId, &needSeg, timelines);
|
||||
|
||||
if (verbose)
|
||||
printf(_("all necessary files are found.\n"));
|
||||
}
|
||||
|
||||
/* create recovery.conf */
|
||||
create_recovery_conf(target_time, target_xid, target_inclusive, target_tli);
|
||||
|
||||
/* release catalog lock */
|
||||
catalog_unlock();
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(backups, pgBackupFree);
|
||||
parray_free(backups);
|
||||
|
||||
/* print restore complete message */
|
||||
if (verbose && !check)
|
||||
{
|
||||
printf(_("all restore completed\n"));
|
||||
printf(_("========================================\n"));
|
||||
}
|
||||
if (!check)
|
||||
elog(INFO, _("restore complete. Recovery starts automatically when the PostgreSQL server is started."));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate and restore backup.
|
||||
*/
|
||||
void
|
||||
restore_database(pgBackup *backup)
|
||||
{
|
||||
char timestamp[100];
|
||||
char path[MAXPGPATH];
|
||||
char list_path[MAXPGPATH];
|
||||
int ret;
|
||||
parray *files;
|
||||
int i;
|
||||
|
||||
/* confirm block size compatibility */
|
||||
if (backup->block_size != BLCKSZ)
|
||||
elog(ERROR_PG_INCOMPATIBLE,
|
||||
_("BLCKSZ(%d) is not compatible(%d expected)"),
|
||||
backup->block_size, BLCKSZ);
|
||||
if (backup->wal_block_size != XLOG_BLCKSZ)
|
||||
elog(ERROR_PG_INCOMPATIBLE,
|
||||
_("XLOG_BLCKSZ(%d) is not compatible(%d expected)"),
|
||||
backup->wal_block_size, XLOG_BLCKSZ);
|
||||
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
if (verbose && !check)
|
||||
{
|
||||
printf(_("----------------------------------------\n"));
|
||||
printf(_("restoring database from backup %s.\n"), timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate backup files with its size, because load of CRC calculation is
|
||||
* not light.
|
||||
*/
|
||||
pgBackupValidate(backup, true);
|
||||
|
||||
/* make direcotries and symbolic links */
|
||||
pgBackupGetPath(backup, path, lengthof(path), MKDIRS_SH_FILE);
|
||||
if (!check)
|
||||
{
|
||||
char pwd[MAXPGPATH];
|
||||
|
||||
/* keep orginal directory */
|
||||
if (getcwd(pwd, sizeof(pwd)) == NULL)
|
||||
elog(ERROR_SYSTEM, _("can't get current working directoryh: %s"),
|
||||
strerror(errno));
|
||||
|
||||
/* create pgdata directory */
|
||||
dir_create_dir(pgdata, DIR_PERMISSION);
|
||||
|
||||
/* change directory to pgdata */
|
||||
if (chdir(pgdata))
|
||||
elog(ERROR_SYSTEM, _("can't change directoryh: %s"),
|
||||
strerror(errno));
|
||||
|
||||
/* Execute mkdirs.sh */
|
||||
ret = system(path);
|
||||
if (ret != 0)
|
||||
elog(ERROR_SYSTEM, _("can't execute mkdirs.sh: %s"),
|
||||
strerror(errno));
|
||||
|
||||
/* go back to original directory */
|
||||
if (chdir(pwd))
|
||||
elog(ERROR_SYSTEM, _("can't change directoryh: %s"),
|
||||
strerror(errno));
|
||||
}
|
||||
|
||||
/*
|
||||
* get list of files which need to be restored.
|
||||
*/
|
||||
pgBackupGetPath(backup, path, lengthof(path), DATABASE_DIR);
|
||||
pgBackupGetPath(backup, list_path, lengthof(list_path), DATABASE_FILE_LIST);
|
||||
files = dir_read_file_list(path, list_path);
|
||||
for (i = parray_num(files) - 1; i >= 0; i--)
|
||||
{
|
||||
pgFile *file = (pgFile *) parray_get(files, i);
|
||||
|
||||
/* remove files which are not backed up */
|
||||
if (file->write_size == BYTES_INVALID)
|
||||
pgFileFree(parray_remove(files, i));
|
||||
}
|
||||
|
||||
/* restore files into $PGDATA */
|
||||
for (i = 0; i < parray_num(files); i++)
|
||||
{
|
||||
char from_root[MAXPGPATH];
|
||||
pgFile *file = (pgFile *) parray_get(files, i);
|
||||
|
||||
pgBackupGetPath(backup, from_root, lengthof(from_root), DATABASE_DIR);
|
||||
|
||||
/* check for interrupt */
|
||||
if (interrupted)
|
||||
elog(ERROR_INTERRUPTED, _("interrupted during restore database"));
|
||||
|
||||
/* print progress */
|
||||
if (verbose && !check)
|
||||
printf(_("(%d/%lu) %s "), i + 1, (unsigned long) parray_num(files),
|
||||
file->path + strlen(from_root) + 1);
|
||||
|
||||
/* directories are created with mkdirs.sh */
|
||||
if (S_ISDIR(file->mode))
|
||||
{
|
||||
if (verbose && !check)
|
||||
printf(_("directory, skip\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* not backed up */
|
||||
if (file->write_size == BYTES_INVALID)
|
||||
{
|
||||
if (verbose && !check)
|
||||
printf(_("not backed up, skip\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* restore file */
|
||||
if (!check)
|
||||
restore_data_file(from_root, pgdata, file, backup->compress_data);
|
||||
|
||||
/* print size of restored file */
|
||||
if (verbose && !check)
|
||||
printf(_("restored %lu\n"), (unsigned long) file->write_size);
|
||||
}
|
||||
|
||||
/* Delete files which are not in file list. */
|
||||
if (!check)
|
||||
{
|
||||
parray *files_now;
|
||||
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
|
||||
/* re-read file list to change base path to $PGDATA */
|
||||
files = dir_read_file_list(pgdata, list_path);
|
||||
parray_qsort(files, pgFileComparePathDesc);
|
||||
|
||||
/* get list of files restored to pgdata */
|
||||
files_now = parray_new();
|
||||
dir_list_file(files_now, pgdata, pgdata_exclude, true, false);
|
||||
/* to delete from leaf, sort in reversed order */
|
||||
parray_qsort(files_now, pgFileComparePathDesc);
|
||||
|
||||
for (i = 0; i < parray_num(files_now); i++)
|
||||
{
|
||||
pgFile *file = (pgFile *) parray_get(files_now, i);
|
||||
|
||||
/* If the file is not in the file list, delete it */
|
||||
if (parray_bsearch(files, file, pgFileComparePathDesc) == NULL)
|
||||
{
|
||||
if (verbose)
|
||||
printf(_(" delete %s\n"), file->path + strlen(pgdata) + 1);
|
||||
pgFileDelete(file);
|
||||
}
|
||||
}
|
||||
|
||||
parray_walk(files_now, pgFileFree);
|
||||
parray_free(files_now);
|
||||
}
|
||||
|
||||
/* remove postmaster.pid */
|
||||
snprintf(path, lengthof(path), "%s/postmaster.pid", pgdata);
|
||||
if (remove(path) == -1 && errno != ENOENT)
|
||||
elog(ERROR_SYSTEM, _("can't remove postmaster.pid: %s"),
|
||||
strerror(errno));
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
|
||||
if (verbose && !check)
|
||||
printf(_("resotre backup completed\n"));
|
||||
}
|
||||
|
||||
/*
|
||||
* Restore archived WAL by creating symbolic link which linked to backup WAL in
|
||||
* archive directory.
|
||||
*/
|
||||
void
|
||||
restore_archive_logs(pgBackup *backup)
|
||||
{
|
||||
int i;
|
||||
char timestamp[100];
|
||||
parray *files;
|
||||
char path[MAXPGPATH];
|
||||
char list_path[MAXPGPATH];
|
||||
char base_path[MAXPGPATH];
|
||||
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
if (verbose && !check)
|
||||
{
|
||||
printf(_("----------------------------------------\n"));
|
||||
printf(_("restoring WAL from backup %s.\n"), timestamp);
|
||||
}
|
||||
|
||||
pgBackupGetPath(backup, list_path, lengthof(list_path), ARCLOG_FILE_LIST);
|
||||
pgBackupGetPath(backup, base_path, lengthof(list_path), ARCLOG_DIR);
|
||||
files = dir_read_file_list(base_path, list_path);
|
||||
for (i = 0; i < parray_num(files); i++)
|
||||
{
|
||||
pgFile *file = (pgFile *) parray_get(files, i);
|
||||
|
||||
/* check for interrupt */
|
||||
if (interrupted)
|
||||
elog(ERROR_INTERRUPTED, _("interrupted during restore WAL"));
|
||||
|
||||
/* print progress */
|
||||
snprintf(path, lengthof(path), "%s/%s", arclog_path,
|
||||
file->path + strlen(base_path) + 1);
|
||||
if (verbose && !check)
|
||||
printf(_("(%d/%lu) %s "), i + 1, (unsigned long) parray_num(files),
|
||||
file->path + strlen(base_path) + 1);
|
||||
|
||||
/* skip files which are not in backup */
|
||||
if (file->write_size == BYTES_INVALID)
|
||||
{
|
||||
if (verbose && !check)
|
||||
printf(_("skip(not backed up)\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* skip timeline history files because timeline history files will be
|
||||
* restored from $BACKUP_PATH/timeline_history.
|
||||
*/
|
||||
if (strstr(file->path, ".history") ==
|
||||
file->path + strlen(file->path) - strlen(".history"))
|
||||
{
|
||||
if (verbose && !check)
|
||||
printf(_("skip(timeline history)\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!check)
|
||||
{
|
||||
if (backup->compress_data)
|
||||
{
|
||||
copy_file(base_path, arclog_path, file, DECOMPRESSION);
|
||||
if (verbose)
|
||||
printf(_("decompressed\n"));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* even same file exist, use backup file */
|
||||
if ((remove(path) == -1) && errno != ENOENT)
|
||||
elog(ERROR_SYSTEM, _("can't remove file \"%s\": %s"), path,
|
||||
strerror(errno));
|
||||
|
||||
if ((symlink(file->path, path) == -1))
|
||||
elog(ERROR_SYSTEM, _("can't create link to \"%s\": %s"),
|
||||
file->path, strerror(errno));
|
||||
|
||||
if (verbose)
|
||||
printf(_("linked\n"));
|
||||
}
|
||||
}
|
||||
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
}
|
||||
|
||||
static void
|
||||
create_recovery_conf(const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
FILE *fp;
|
||||
|
||||
if (verbose && !check)
|
||||
{
|
||||
printf(_("----------------------------------------\n"));
|
||||
printf(_("creating recovery.conf\n"));
|
||||
}
|
||||
|
||||
if (!check)
|
||||
{
|
||||
snprintf(path, lengthof(path), "%s/recovery.conf", pgdata);
|
||||
fp = fopen(path, "wt");
|
||||
if (fp == NULL)
|
||||
elog(ERROR_SYSTEM, _("can't open recovery.conf \"%s\": %s"), path,
|
||||
strerror(errno));
|
||||
|
||||
fprintf(fp, "# recovery.conf generated by pg_rman %s\n",
|
||||
PROGRAM_VERSION);
|
||||
fprintf(fp, "restore_command = 'cp %s/%%f %%p'\n", arclog_path);
|
||||
if (target_time)
|
||||
fprintf(fp, "recovery_target_time = '%s'\n", target_time);
|
||||
if (target_xid)
|
||||
fprintf(fp, "recovery_target_xid = '%s'\n", target_xid);
|
||||
if (target_inclusive)
|
||||
fprintf(fp, "recovery_target_inclusive = '%s'\n", target_inclusive);
|
||||
fprintf(fp, "recovery_target_timeline = '%u'\n", target_tli);
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
backup_online_files(bool re_recovery)
|
||||
{
|
||||
char work_path[MAXPGPATH];
|
||||
char pg_xlog_path[MAXPGPATH];
|
||||
bool files_exist;
|
||||
parray *files;
|
||||
|
||||
if (verbose && !check)
|
||||
{
|
||||
printf(_("----------------------------------------\n"));
|
||||
printf(_("backup online WAL and serverlog start\n"));
|
||||
}
|
||||
|
||||
/* get list of files in $BACKUP_PATH/backup/pg_xlog */
|
||||
files = parray_new();
|
||||
snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path,
|
||||
RESTORE_WORK_DIR, PG_XLOG_DIR);
|
||||
dir_list_file(files, work_path, NULL, true, false);
|
||||
|
||||
files_exist = parray_num(files) > 0;
|
||||
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
|
||||
/* If files exist in RESTORE_WORK_DIR and not re-recovery, use them. */
|
||||
if (files_exist && !re_recovery)
|
||||
{
|
||||
if (verbose)
|
||||
printf(_("online WALs have been already backed up, use them.\n"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* backup online WAL */
|
||||
snprintf(pg_xlog_path, lengthof(pg_xlog_path), "%s/pg_xlog", pgdata);
|
||||
snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path,
|
||||
RESTORE_WORK_DIR, PG_XLOG_DIR);
|
||||
dir_create_dir(work_path, DIR_PERMISSION);
|
||||
dir_copy_files(pg_xlog_path, work_path);
|
||||
|
||||
/* backup serverlog */
|
||||
snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path,
|
||||
RESTORE_WORK_DIR, SRVLOG_DIR);
|
||||
dir_create_dir(work_path, DIR_PERMISSION);
|
||||
dir_copy_files(srvlog_path, work_path);
|
||||
}
|
||||
|
||||
static void
|
||||
restore_online_files(void)
|
||||
{
|
||||
int i;
|
||||
char root_backup[MAXPGPATH];
|
||||
parray *files_backup;
|
||||
|
||||
/* get list of files in $BACKUP_PATH/backup/pg_xlog */
|
||||
files_backup = parray_new();
|
||||
snprintf(root_backup, lengthof(root_backup), "%s/%s/%s", backup_path,
|
||||
RESTORE_WORK_DIR, PG_XLOG_DIR);
|
||||
dir_list_file(files_backup, root_backup, NULL, true, false);
|
||||
|
||||
if (verbose && !check)
|
||||
{
|
||||
printf(_("----------------------------------------\n"));
|
||||
printf(_("restoring online WAL\n"));
|
||||
}
|
||||
|
||||
/* restore online WAL */
|
||||
for (i = 0; i < parray_num(files_backup); i++)
|
||||
{
|
||||
pgFile *file = (pgFile *) parray_get(files_backup, i);
|
||||
|
||||
if (S_ISDIR(file->mode))
|
||||
{
|
||||
char to_path[MAXPGPATH];
|
||||
snprintf(to_path, lengthof(to_path), "%s/%s/%s", pgdata,
|
||||
PG_XLOG_DIR, file->path + strlen(root_backup) + 1);
|
||||
if (verbose && !check)
|
||||
printf(_("create directory \"%s\"\n"),
|
||||
file->path + strlen(root_backup) + 1);
|
||||
if (!check)
|
||||
dir_create_dir(to_path, DIR_PERMISSION);
|
||||
continue;
|
||||
}
|
||||
else if(S_ISREG(file->mode))
|
||||
{
|
||||
char to_root[MAXPGPATH];
|
||||
snprintf(to_root, lengthof(to_root), "%s/%s", pgdata, PG_XLOG_DIR);
|
||||
if (verbose && !check)
|
||||
printf(_("restore \"%s\"\n"),
|
||||
file->path + strlen(root_backup) + 1);
|
||||
if (!check)
|
||||
copy_file(root_backup, to_root, file, NO_COMPRESSION);
|
||||
}
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(files_backup, pgFileFree);
|
||||
parray_free(files_backup);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to read a timeline's history file.
|
||||
*
|
||||
* If successful, return the list of component pgTimeLine (the ancestor
|
||||
* timelines followed by target timeline). If we can't find the history file,
|
||||
* assume that the timeline has no parents, and return a list of just the
|
||||
* specified timeline ID.
|
||||
* based on readTimeLineHistory() in xlog.c
|
||||
*/
|
||||
static parray *
|
||||
readTimeLineHistory(TimeLineID targetTLI)
|
||||
{
|
||||
parray *result;
|
||||
char path[MAXPGPATH];
|
||||
char fline[MAXPGPATH];
|
||||
FILE *fd;
|
||||
pgTimeLine *timeline;
|
||||
pgTimeLine *last_timeline = NULL;
|
||||
|
||||
result = parray_new();
|
||||
|
||||
/* search from arclog_path first */
|
||||
snprintf(path, lengthof(path), "%s/%08X.history", arclog_path,
|
||||
targetTLI);
|
||||
fd = fopen(path, "rt");
|
||||
if (fd == NULL)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
elog(ERROR_SYSTEM, _("could not open file \"%s\": %s"), path,
|
||||
strerror(errno));
|
||||
|
||||
/* search from restore work directory next */
|
||||
snprintf(path, lengthof(path), "%s/%s/%s/%08X.history", backup_path,
|
||||
RESTORE_WORK_DIR, PG_XLOG_DIR, targetTLI);
|
||||
fd = fopen(path, "rt");
|
||||
if (fd == NULL)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
elog(ERROR_SYSTEM, _("could not open file \"%s\": %s"), path,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the file...
|
||||
*/
|
||||
while (fd && fgets(fline, sizeof(fline), fd) != NULL)
|
||||
{
|
||||
/* skip leading whitespace and check for # comment */
|
||||
char *ptr;
|
||||
char *endptr;
|
||||
|
||||
for (ptr = fline; *ptr; ptr++)
|
||||
{
|
||||
if (!IsSpace(*ptr))
|
||||
break;
|
||||
}
|
||||
if (*ptr == '\0' || *ptr == '#')
|
||||
continue;
|
||||
|
||||
timeline = pgut_malloc(sizeof(*timeline));
|
||||
timeline->tli = 0;
|
||||
timeline->end.xlogid = 0;
|
||||
timeline->end.xrecoff = 0;
|
||||
|
||||
/* expect a numeric timeline ID as first field of line */
|
||||
timeline->tli = (TimeLineID) strtoul(ptr, &endptr, 0);
|
||||
if (endptr == ptr)
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("syntax error(timeline ID) in history file: %s"),
|
||||
fline);
|
||||
|
||||
if (last_timeline && timeline->tli <= last_timeline->tli)
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("Timeline IDs must be in increasing sequence."));
|
||||
|
||||
/* Build list with newest item first */
|
||||
parray_insert(result, 0, timeline);
|
||||
last_timeline = timeline;
|
||||
|
||||
/* parse end point(logfname, xid) in the timeline */
|
||||
for (ptr = endptr; *ptr; ptr++)
|
||||
{
|
||||
if (!IsSpace(*ptr))
|
||||
break;
|
||||
}
|
||||
if (*ptr == '\0' || *ptr == '#')
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("End logfile must follow Timeline ID."));
|
||||
|
||||
if (!xlog_logfname2lsn(ptr, &timeline->end))
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("syntax error(endfname) in history file: %s"), fline);
|
||||
/* we ignore the remainder of each line */
|
||||
}
|
||||
|
||||
if (fd)
|
||||
fclose(fd);
|
||||
|
||||
if (last_timeline && targetTLI <= last_timeline->tli)
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("Timeline IDs must be less than child timeline's ID."));
|
||||
|
||||
/* append target timeline */
|
||||
timeline = pgut_malloc(sizeof(*timeline));
|
||||
timeline->tli = targetTLI;
|
||||
timeline->end.xlogid = (uint32) -1; /* lsn in target timelie is valid */
|
||||
timeline->end.xrecoff = (uint32) -1; /* lsn target timelie is valid */
|
||||
parray_insert(result, 0, timeline);
|
||||
|
||||
/* dump timeline branches for debug */
|
||||
if (debug)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < parray_num(result); i++)
|
||||
{
|
||||
pgTimeLine *timeline = parray_get(result, i);
|
||||
elog(LOG, "%s() result[%d]: %08X/%08X/%08X", __FUNCTION__, i,
|
||||
timeline->tli, timeline->end.xlogid, timeline->end.xrecoff);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
satisfy_timeline(const parray *timelines, const pgBackup *backup)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < parray_num(timelines); i++)
|
||||
{
|
||||
pgTimeLine *timeline = (pgTimeLine *) parray_get(timelines, i);
|
||||
if (backup->tli == timeline->tli &&
|
||||
XLByteLT(backup->stop_lsn, timeline->end))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* get TLI of the current database */
|
||||
static TimeLineID
|
||||
get_current_timeline(void)
|
||||
{
|
||||
ControlFileData ControlFile;
|
||||
int fd;
|
||||
char ControlFilePath[MAXPGPATH];
|
||||
pg_crc32 crc;
|
||||
TimeLineID ret;
|
||||
|
||||
snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", pgdata);
|
||||
|
||||
if ((fd = open(ControlFilePath, O_RDONLY | PG_BINARY, 0)) == -1)
|
||||
{
|
||||
elog(WARNING, _("can't open pg_controldata file \"%s\": %s"),
|
||||
ControlFilePath, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (read(fd, &ControlFile, sizeof(ControlFileData)) != sizeof(ControlFileData))
|
||||
{
|
||||
elog(WARNING, _("can't read pg_controldata file \"%s\": %s"),
|
||||
ControlFilePath, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
/* Check the CRC. */
|
||||
INIT_CRC32(crc);
|
||||
COMP_CRC32(crc,
|
||||
(char *) &ControlFile,
|
||||
offsetof(ControlFileData, crc));
|
||||
FIN_CRC32(crc);
|
||||
|
||||
if (!EQ_CRC32(crc, ControlFile.crc))
|
||||
{
|
||||
elog(WARNING, _("Calculated CRC checksum does not match value stored in file.\n"
|
||||
"Either the file is corrupt, or it has a different layout than this program\n"
|
||||
"is expecting. The results below are untrustworthy.\n"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ControlFile.pg_control_version % 65536 == 0 && ControlFile.pg_control_version / 65536 != 0)
|
||||
{
|
||||
elog(WARNING, _("possible byte ordering mismatch\n"
|
||||
"The byte ordering used to store the pg_control file might not match the one\n"
|
||||
"used by this program. In that case the results below would be incorrect, and\n"
|
||||
"the PostgreSQL installation would be incompatible with this data directory.\n"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = ControlFile.checkPointCopy.ThisTimeLineID;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* get TLI of the latest full backup */
|
||||
static TimeLineID
|
||||
get_fullbackup_timeline(parray *backups)
|
||||
{
|
||||
int i;
|
||||
pgBackup *base_backup = NULL;
|
||||
TimeLineID ret;
|
||||
|
||||
for (i = 0; i < parray_num(backups); i++)
|
||||
{
|
||||
base_backup = (pgBackup *) parray_get(backups, i);
|
||||
|
||||
if (base_backup->backup_mode >= BACKUP_MODE_FULL)
|
||||
{
|
||||
/*
|
||||
* Validate backup files with its size, because load of CRC
|
||||
* calculation is not light.
|
||||
*/
|
||||
if (base_backup->status == BACKUP_STATUS_DONE)
|
||||
pgBackupValidate(base_backup, true);
|
||||
|
||||
if (base_backup->status == BACKUP_STATUS_OK)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* no full backup found, can't restore */
|
||||
if (i == parray_num(backups))
|
||||
elog(ERROR_NO_BACKUP, _("no full backup found, can't restore."));
|
||||
|
||||
ret = base_backup->tli;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
print_backup_id(const pgBackup *backup)
|
||||
{
|
||||
char timestamp[100];
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
printf(_(" %s (%X/%08X)\n"), timestamp, backup->stop_lsn.xlogid,
|
||||
backup->stop_lsn.xrecoff);
|
||||
}
|
||||
|
||||
static void
|
||||
search_next_wal(const char *path, uint32 *needId, uint32 *needSeg, parray *timelines)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int count;
|
||||
char xlogfname[MAXFNAMELEN];
|
||||
char pre_xlogfname[MAXFNAMELEN];
|
||||
char xlogpath[MAXPGPATH];
|
||||
struct stat st;
|
||||
|
||||
count = 0;
|
||||
for (;;)
|
||||
{
|
||||
for (i = 0; i < parray_num(timelines); i++)
|
||||
{
|
||||
pgTimeLine *timeline = (pgTimeLine *) parray_get(timelines, i);
|
||||
|
||||
XLogFileName(xlogfname, timeline->tli, *needId, *needSeg);
|
||||
snprintf(xlogpath, lengthof(xlogpath), "%s/%s", path, xlogfname);
|
||||
|
||||
if (stat(xlogpath, &st) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* not found */
|
||||
if (i == parray_num(timelines))
|
||||
{
|
||||
if (count == 1)
|
||||
printf(_("\n"));
|
||||
else if (count > 1)
|
||||
printf(_(" - %s\n"), pre_xlogfname);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count == 1)
|
||||
printf(_("%s"), xlogfname);
|
||||
|
||||
strcpy(pre_xlogfname, xlogfname);
|
||||
|
||||
/* delete old TLI */
|
||||
for (j = i + 1; j < parray_num(timelines); j++)
|
||||
parray_remove(timelines, i + 1);
|
||||
/* XXX: should we add a linebreak when we find a timeline? */
|
||||
|
||||
NextLogSeg(*needId, *needSeg);
|
||||
}
|
||||
}
|
255
show.c
Normal file
255
show.c
Normal file
@ -0,0 +1,255 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* show.c: show backup catalog.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
static void show_backup_list(FILE *out, parray *backup_list, bool show_all);
|
||||
static void show_timeline_backup_list(FILE *out, parray *backup_list, bool show_all);
|
||||
static void show_backup_detail(FILE *out, pgBackup *backup);
|
||||
|
||||
/*
|
||||
* Show backup catalog information.
|
||||
* If range is { 0, 0 }, show list of all backup, otherwise show detail of the
|
||||
* backup indicated by id.
|
||||
*/
|
||||
int
|
||||
do_show(pgBackupRange *range, bool show_timeline, bool show_all)
|
||||
{
|
||||
if (pgBackupRangeIsSingle(range))
|
||||
{
|
||||
pgBackup *backup;
|
||||
|
||||
backup = catalog_get_backup(range->begin);
|
||||
if (backup == NULL)
|
||||
{
|
||||
char timestamp[100];
|
||||
time2iso(timestamp, lengthof(timestamp), range->begin);
|
||||
elog(INFO, _("backup taken at \"%s\" doesn not exist."),
|
||||
timestamp);
|
||||
/* This is not error case */
|
||||
return 0;
|
||||
}
|
||||
show_backup_detail(stdout, backup);
|
||||
|
||||
/* cleanup */
|
||||
pgBackupFree(backup);
|
||||
}
|
||||
else
|
||||
{
|
||||
parray *backup_list;
|
||||
|
||||
backup_list = catalog_get_backup_list(range);
|
||||
if (backup_list == NULL)
|
||||
return 1;
|
||||
|
||||
if (!show_timeline)
|
||||
show_backup_list(stdout, backup_list, show_all);
|
||||
else
|
||||
show_timeline_backup_list(stdout, backup_list, show_all);
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(backup_list, pgBackupFree);
|
||||
parray_free(backup_list);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pretty_size(int64 size, char *buf, size_t len)
|
||||
{
|
||||
int exp = 0;
|
||||
|
||||
/* minus means the size is invalid */
|
||||
if (size < 0)
|
||||
{
|
||||
strncpy(buf, "----", len);
|
||||
return;
|
||||
}
|
||||
|
||||
/* determine postfix */
|
||||
while (size > 9999)
|
||||
{
|
||||
++exp;
|
||||
size /= 1000;
|
||||
}
|
||||
|
||||
switch (exp)
|
||||
{
|
||||
case 0:
|
||||
snprintf(buf, len, INT64_FORMAT "B", size);
|
||||
break;
|
||||
case 1:
|
||||
snprintf(buf, len, INT64_FORMAT "kB", size);
|
||||
break;
|
||||
case 2:
|
||||
snprintf(buf, len, INT64_FORMAT "MB", size);
|
||||
break;
|
||||
case 3:
|
||||
snprintf(buf, len, INT64_FORMAT "GB", size);
|
||||
break;
|
||||
case 4:
|
||||
snprintf(buf, len, INT64_FORMAT "TB", size);
|
||||
break;
|
||||
case 5:
|
||||
snprintf(buf, len, INT64_FORMAT "PB", size);
|
||||
break;
|
||||
default:
|
||||
strncpy(buf, "***", len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static TimeLineID
|
||||
get_parent_tli(TimeLineID child_tli)
|
||||
{
|
||||
TimeLineID result = 0;
|
||||
char path[MAXPGPATH];
|
||||
char fline[MAXPGPATH];
|
||||
FILE *fd;
|
||||
|
||||
/* search from timeline history dir */
|
||||
snprintf(path, lengthof(path), "%s/%s/%08X.history", backup_path,
|
||||
TIMELINE_HISTORY_DIR, child_tli);
|
||||
fd = fopen(path, "rt");
|
||||
if (fd == NULL)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
elog(ERROR_SYSTEM, _("could not open file \"%s\": %s"), path,
|
||||
strerror(errno));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the file...
|
||||
*/
|
||||
while (fgets(fline, sizeof(fline), fd) != NULL)
|
||||
{
|
||||
/* skip leading whitespace and check for # comment */
|
||||
char *ptr;
|
||||
char *endptr;
|
||||
|
||||
for (ptr = fline; *ptr; ptr++)
|
||||
{
|
||||
if (!IsSpace(*ptr))
|
||||
break;
|
||||
}
|
||||
if (*ptr == '\0' || *ptr == '#')
|
||||
continue;
|
||||
|
||||
/* expect a numeric timeline ID as first field of line */
|
||||
result = (TimeLineID) strtoul(ptr, &endptr, 0);
|
||||
if (endptr == ptr)
|
||||
elog(ERROR_CORRUPTED,
|
||||
_("syntax error(timeline ID) in history file: %s"),
|
||||
fline);
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
|
||||
/* TLI of the last line is parent TLI */
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
show_backup_list(FILE *out, parray *backup_list, bool show_all)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* show header */
|
||||
fputs("============================================================================\n", out);
|
||||
fputs("Start Time Total Data WAL Log Backup Status \n", out);
|
||||
fputs("============================================================================\n", out);
|
||||
|
||||
for (i = 0; i < parray_num(backup_list); i++)
|
||||
{
|
||||
pgBackup *backup;
|
||||
char timestamp[20];
|
||||
char duration[20] = "----";
|
||||
char total_data_bytes_str[10] = "----";
|
||||
char read_data_bytes_str[10] = "----";
|
||||
char read_arclog_bytes_str[10] = "----";
|
||||
char read_srvlog_bytes_str[10] = "----";
|
||||
char write_bytes_str[10];
|
||||
|
||||
backup = parray_get(backup_list, i);
|
||||
|
||||
/* skip deleted backup */
|
||||
if (backup->status == BACKUP_STATUS_DELETED && !show_all)
|
||||
continue;
|
||||
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
if (backup->end_time != (time_t) 0)
|
||||
snprintf(duration, lengthof(duration), "%lum",
|
||||
(backup->end_time - backup->start_time) / 60);
|
||||
/* "Full" is only for full backup */
|
||||
if (backup->backup_mode >= BACKUP_MODE_FULL)
|
||||
pretty_size(backup->total_data_bytes, total_data_bytes_str,
|
||||
lengthof(total_data_bytes_str));
|
||||
else if (backup->backup_mode >= BACKUP_MODE_INCREMENTAL)
|
||||
pretty_size(backup->read_data_bytes, read_data_bytes_str,
|
||||
lengthof(read_data_bytes_str));
|
||||
if (HAVE_ARCLOG(backup))
|
||||
pretty_size(backup->read_arclog_bytes, read_arclog_bytes_str,
|
||||
lengthof(read_arclog_bytes_str));
|
||||
if (backup->with_serverlog)
|
||||
pretty_size(backup->read_srvlog_bytes, read_srvlog_bytes_str,
|
||||
lengthof(read_srvlog_bytes_str));
|
||||
pretty_size(backup->write_bytes, write_bytes_str,
|
||||
lengthof(write_bytes_str));
|
||||
|
||||
fprintf(out, "%-19s %5s %6s %6s %6s %6s %6s %s\n",
|
||||
timestamp, duration,
|
||||
total_data_bytes_str, read_data_bytes_str, read_arclog_bytes_str,
|
||||
read_srvlog_bytes_str, write_bytes_str, status2str(backup->status));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
show_timeline_backup_list(FILE *out, parray *backup_list, bool show_all)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* show header */
|
||||
fputs("============================================================\n", out);
|
||||
fputs("Start Mode Current TLI Parent TLI Status \n", out);
|
||||
fputs("============================================================\n", out);
|
||||
|
||||
for (i = 0; i < parray_num(backup_list); i++)
|
||||
{
|
||||
static const char *modes[] = { "", "ARCH", "INCR", "FULL"};
|
||||
|
||||
pgBackup *backup;
|
||||
char timestamp[20];
|
||||
TimeLineID parent_tli;
|
||||
|
||||
backup = parray_get(backup_list, i);
|
||||
|
||||
/* skip deleted backup and serverlog backup */
|
||||
if ((backup->status == BACKUP_STATUS_DELETED || !HAVE_ARCLOG(backup)) &&
|
||||
!show_all)
|
||||
continue;
|
||||
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
|
||||
parent_tli = get_parent_tli(backup->tli);
|
||||
|
||||
fprintf(out, "%-19s %-4s %10d %10d %s\n",
|
||||
timestamp, modes[backup->backup_mode], backup->tli, parent_tli,
|
||||
status2str(backup->status));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
show_backup_detail(FILE *out, pgBackup *backup)
|
||||
{
|
||||
pgBackupWriteConfigSection(out, backup);
|
||||
pgBackupWriteResultSection(out, backup);
|
||||
}
|
3
sql/backup.sql
Normal file
3
sql/backup.sql
Normal file
@ -0,0 +1,3 @@
|
||||
\! pg_rman backup --verbose
|
||||
\! pg_rman backup -B data/sample_backup --verbose
|
||||
|
274
sql/backup_restore.sh
Normal file
274
sql/backup_restore.sh
Normal file
@ -0,0 +1,274 @@
|
||||
#!/bin/sh
|
||||
|
||||
BASE_PATH=`pwd`
|
||||
export PGDATA=$BASE_PATH/results/sample_database
|
||||
|
||||
export BACKUP_PATH=$BASE_PATH/results/sample_backup2
|
||||
export ARCLOG_PATH=$BASE_PATH/results/arclog
|
||||
export SRVLOG_PATH=$PGDATA/pg_log
|
||||
export COMPRESS_DATA=YES
|
||||
XLOG_PATH=$PGDATA/pg_xlog
|
||||
TBLSPC_PATH=$BASE_PATH/results/tblspc
|
||||
|
||||
# Port used for test database cluster
|
||||
TEST_PGPORT=54321
|
||||
|
||||
# configuration
|
||||
SCALE=1
|
||||
DURATION=10
|
||||
ISOLATE_SRVLOG=0
|
||||
ISOLATE_WAL=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
"-s")
|
||||
ISOLATE_SRVLOG=1
|
||||
shift
|
||||
;;
|
||||
"-w")
|
||||
ISOLATE_WAL=1
|
||||
shift
|
||||
;;
|
||||
"-d")
|
||||
DURATION=`expr $2 + 0`
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "invalid duration"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
"-s")
|
||||
SCALE=`expr $2 + 0`
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "invalid scale"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# delete old database cluster
|
||||
pg_ctl stop -m immediate > /dev/null 2>&1
|
||||
rm -rf $PGDATA
|
||||
rm -rf $BASE_PATH/results/pg_xlog
|
||||
rm -rf $BASE_PATH/results/srvlog
|
||||
rm -rf $ARCLOG_PATH
|
||||
rm -rf $SRVLOG_PATH
|
||||
rm -rf $TBLSPC_PATH
|
||||
|
||||
# create new backup catalog
|
||||
rm -rf $BACKUP_PATH
|
||||
pg_rman init -B $BACKUP_PATH --quiet
|
||||
|
||||
# create default configuration file
|
||||
cat << EOF > $BACKUP_PATH/pg_rman.ini
|
||||
# comment
|
||||
BACKUP_MODE = F # comment
|
||||
EOF
|
||||
|
||||
# create new database cluster
|
||||
initdb --no-locale > $BASE_PATH/results/initdb.log 2>&1
|
||||
cat << EOF >> $PGDATA/postgresql.conf
|
||||
port = $TEST_PGPORT
|
||||
logging_collector = on
|
||||
archive_mode = on
|
||||
archive_command = 'cp "%p" "$ARCLOG_PATH/%f"'
|
||||
EOF
|
||||
|
||||
mkdir -p $ARCLOG_PATH
|
||||
mkdir -p $TBLSPC_PATH
|
||||
|
||||
# determine serverlog directory
|
||||
if [ "$ISOLATE_SRVLOG" -ne 0 ]; then
|
||||
export SRVLOG_PATH=$BASE_PATH/results/srvlog
|
||||
echo "log_directory = '$SRVLOG_PATH'" >> $PGDATA/postgresql.conf
|
||||
mkdir -p $SRVLOG_PATH
|
||||
else
|
||||
export SRVLOG_PATH=$PGDATA/pg_log
|
||||
echo "log_directory = 'pg_log'" >> $PGDATA/postgresql.conf
|
||||
fi
|
||||
|
||||
# isolate online WAL
|
||||
if [ "$ISOLATE_WAL" -ne 0 ]; then
|
||||
XLOG_PATH=$BASE_PATH/results/pg_xlog
|
||||
mv $PGDATA/pg_xlog $XLOG_PATH
|
||||
ln -s $XLOG_PATH $PGDATA/pg_xlog
|
||||
fi
|
||||
|
||||
# start PostgreSQL
|
||||
pg_ctl start -w -t 3600 > /dev/null 2>&1
|
||||
|
||||
# create tablespace and database for pgbench
|
||||
mkdir -p $TBLSPC_PATH/pgbench
|
||||
psql -p $TEST_PGPORT postgres <<EOF
|
||||
CREATE TABLESPACE pgbench LOCATION '$TBLSPC_PATH/pgbench';
|
||||
CREATE DATABASE pgbench TABLESPACE = pgbench;
|
||||
EOF
|
||||
|
||||
# data_delete
|
||||
export KEEP_DATA_GENERATIONS=2
|
||||
export KEEP_DATA_DAYS=0
|
||||
for i in `seq 1 5`; do
|
||||
pg_rman -p $TEST_PGPORT backup --verbose -d postgres > $BASE_PATH/results/log_full_0_$i 2>&1
|
||||
done
|
||||
pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show_d_1 2>&1
|
||||
echo "# of deleted backups"
|
||||
grep -c DELETED $BASE_PATH/results/log_show_d_1
|
||||
|
||||
pgbench -p $TEST_PGPORT -i -s $SCALE pgbench > $BASE_PATH/results/pgbench.log 2>&1
|
||||
|
||||
echo "full database backup"
|
||||
psql -p $TEST_PGPORT postgres -c "checkpoint"
|
||||
pg_rman -p $TEST_PGPORT backup --verbose -d postgres > $BASE_PATH/results/log_full_1 2>&1
|
||||
|
||||
pgbench -p $TEST_PGPORT -T $DURATION -c 10 pgbench >> $BASE_PATH/results/pgbench.log 2>&1
|
||||
echo "incremental database backup"
|
||||
psql -p $TEST_PGPORT postgres -c "checkpoint"
|
||||
pg_rman -p $TEST_PGPORT backup -b i --verbose -d postgres > $BASE_PATH/results/log_incr1 2>&1
|
||||
|
||||
# validate all backup
|
||||
pg_rman validate `date +%Y` --verbose > $BASE_PATH/results/log_validate1 2>&1
|
||||
pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show0 2>&1
|
||||
pg_dumpall > $BASE_PATH/results/dump_before_rtx.sql
|
||||
target_xid=`psql -p $TEST_PGPORT pgbench -tAq -c "INSERT INTO pgbench_history VALUES (1) RETURNING(xmin);"`
|
||||
psql -p $TEST_PGPORT postgres -c "checkpoint"
|
||||
pg_rman -p $TEST_PGPORT backup -b i --verbose -d postgres > $BASE_PATH/results/log_incr2 2>&1
|
||||
|
||||
pgbench -p $TEST_PGPORT -T $DURATION -c 10 pgbench >> $BASE_PATH/results/pgbench.log 2>&1
|
||||
echo "archived WAL and serverlog backup"
|
||||
pg_rman -p $TEST_PGPORT backup -b a --verbose -d postgres > $BASE_PATH/results/log_arclog 2>&1
|
||||
|
||||
# stop PG during transaction and get commited info for verifing
|
||||
echo "stop DB during running pgbench"
|
||||
pgbench -p $TEST_PGPORT -T $DURATION -c 10 pgbench >> $BASE_PATH/results/pgbench.log 2>&1 &
|
||||
sleep `expr $DURATION / 2`
|
||||
pg_ctl stop -m immediate > /dev/null 2>&1
|
||||
cp -rp $PGDATA $PGDATA.bak
|
||||
pg_ctl start -w -t 3600 > /dev/null 2>&1
|
||||
pg_dumpall > $BASE_PATH/results/dump_before.sql
|
||||
|
||||
# revert to crushed cluster
|
||||
pg_ctl stop > /dev/null 2>&1
|
||||
rm -rf $PGDATA
|
||||
mv $PGDATA.bak $PGDATA
|
||||
|
||||
# validate all backup
|
||||
pg_rman validate `date +%Y` --verbose > $BASE_PATH/results/log_validate2 2>&1
|
||||
|
||||
# restore check with pg_rman
|
||||
pg_rman restore -! --verbose --check > $BASE_PATH/results/log_restore_check_1 2>&1
|
||||
|
||||
# restore with pg_rman
|
||||
CUR_TLI=`pg_controldata | grep TimeLineID | awk '{print $4}'`
|
||||
pg_rman restore -! --verbose > $BASE_PATH/results/log_restore1_1 2>&1
|
||||
CUR_TLI_R=`grep "current timeline ID = " $BASE_PATH/results/log_restore1_1 | awk '{print $5}'`
|
||||
TARGET_TLI=`grep "target timeline ID = " $BASE_PATH/results/log_restore1_1 | awk '{print $5}'`
|
||||
if [ "$CUR_TLI" != "$CUR_TLI_R" -o "$CUR_TLI" != "$CUR_TLI_R" ]; then
|
||||
echo "failed: bad timeline ID" CUR_TLI=$CUR_TLI CUR_TLI_R=$CUR_TLI_R
|
||||
fi
|
||||
|
||||
# Backup of online-WAL and serverlog.
|
||||
echo "diff files in BACKUP_PATH/backup/pg_xlog"
|
||||
diff -r $PGDATA/pg_xlog $BACKUP_PATH/backup/pg_xlog
|
||||
echo "# of files in BACKUP_PATH/backup/srvlog"
|
||||
find $BACKUP_PATH/backup/srvlog -type f | wc -l
|
||||
|
||||
# recovery database
|
||||
pg_ctl start -w -t 3600 > /dev/null 2>&1
|
||||
|
||||
# re-restore with pg_rman
|
||||
pg_ctl stop -m immediate > /dev/null 2>&1
|
||||
|
||||
# restore check with pg_rman
|
||||
pg_rman restore -! --verbose --check > $BASE_PATH/results/log_restore_check_2 2>&1
|
||||
|
||||
CUR_TLI=`pg_controldata | grep TimeLineID | awk '{print $4}'`
|
||||
pg_rman restore -! --verbose > $BASE_PATH/results/log_restore1_2 2>&1
|
||||
CUR_TLI_R=`grep "current timeline ID = " $BASE_PATH/results/log_restore1_2 | awk '{print $5}'`
|
||||
TARGET_TLI=`grep "target timeline ID = " $BASE_PATH/results/log_restore1_2 | awk '{print $5}'`
|
||||
if [ "$CUR_TLI" != "$CUR_TLI_R" -o "$CUR_TLI" != "$CUR_TLI_R" ]; then
|
||||
echo "failed: bad timeline ID" CUR_TLI=$CUR_TLI CUR_TLI_R=$CUR_TLI_R
|
||||
fi
|
||||
|
||||
# Backup of online-WAL and serverlog.
|
||||
echo "diff files in BACKUP_PATH/backup/pg_xlog"
|
||||
diff -r $PGDATA/pg_xlog $BACKUP_PATH/backup/pg_xlog
|
||||
echo "# of files in BACKUP_PATH/backup/srvlog"
|
||||
find $BACKUP_PATH/backup/srvlog -type f | wc -l
|
||||
|
||||
# re-recovery database
|
||||
pg_ctl start -w -t 3600 > /dev/null 2>&1
|
||||
|
||||
# compare recovery results
|
||||
pg_dumpall > $BASE_PATH/results/dump_after.sql
|
||||
diff $BASE_PATH/results/dump_before.sql $BASE_PATH/results/dump_after.sql
|
||||
|
||||
# take a backup and delete backed up online files
|
||||
# incrementa backup can't find last full backup because new timeline started.
|
||||
echo "full database backup after recovery"
|
||||
psql -p $TEST_PGPORT postgres -c "checkpoint"
|
||||
pg_rman -p $TEST_PGPORT backup -b f --verbose -d postgres > $BASE_PATH/results/log_full2 2>&1
|
||||
|
||||
# Backup of online-WAL should been deleted, but serverlog remain.
|
||||
echo "# of files in BACKUP_PATH/backup/pg_xlog"
|
||||
find $BACKUP_PATH/backup/pg_xlog -type f | wc -l
|
||||
echo "# of files in BACKUP_PATH/backup/srvlog"
|
||||
find $BACKUP_PATH/backup/srvlog -type f | wc -l
|
||||
|
||||
# Symbolic links in $ARCLOG_PATH should be deleted.
|
||||
echo "# of symbolic links in ARCLOG_PATH"
|
||||
find $ARCLOG_PATH -type l | wc -l
|
||||
|
||||
# timeline history files are backed up.
|
||||
echo "# of files in BACKUP_PATH/timeline_history"
|
||||
find $BACKUP_PATH/timeline_history -type f | wc -l
|
||||
|
||||
# restore with pg_rman
|
||||
pg_ctl stop -m immediate > /dev/null 2>&1
|
||||
|
||||
# restore check with pg_rman
|
||||
pg_rman restore -! --verbose --check > $BASE_PATH/results/log_restore_check_3 2>&1
|
||||
|
||||
CUR_TLI=`pg_controldata | grep TimeLineID | awk '{print $4}'`
|
||||
pg_rman restore -! --recovery-target-xid $target_xid --recovery-target-inclusive false --verbose > $BASE_PATH/results/log_restore2 2>&1
|
||||
CUR_TLI_R=`grep "current timeline ID = " $BASE_PATH/results/log_restore2 | awk '{print $5}'`
|
||||
TARGET_TLI=`grep "target timeline ID = " $BASE_PATH/results/log_restore2 | awk '{print $5}'`
|
||||
if [ "$CUR_TLI" != "$CUR_TLI_R" -o "$CUR_TLI" != "$CUR_TLI_R" ]; then
|
||||
echo "failed: bad timeline ID" CUR_TLI=$CUR_TLI CUR_TLI_R=$CUR_TLI_R
|
||||
fi
|
||||
echo "# of recovery target option in recovery.conf"
|
||||
grep -c "recovery_target_" $PGDATA/recovery.conf
|
||||
|
||||
# recovery database
|
||||
pg_ctl start -w -t 3600 > /dev/null 2>&1
|
||||
|
||||
pg_dumpall > $BASE_PATH/results/dump_after_rtx.sql
|
||||
diff $BASE_PATH/results/dump_before_rtx.sql $BASE_PATH/results/dump_after_rtx.sql
|
||||
|
||||
# show timeline
|
||||
pg_rman -p $TEST_PGPORT show timeline --verbose -a -d postgres > $BASE_PATH/results/log_show_timeline_1 2>&1
|
||||
pg_rman -p $TEST_PGPORT show timeline `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show_timeline_2 2>&1
|
||||
pg_rman -p $TEST_PGPORT show timeline `date +%Y` --verbose -d postgres > $BASE_PATH/results/log_show_timeline_3 2>&1
|
||||
echo "# of deleted backups (show all)"
|
||||
grep -c DELETED $BASE_PATH/results/log_show_timeline_2
|
||||
echo "# of deleted backups"
|
||||
grep -c DELETED $BASE_PATH/results/log_show_timeline_3
|
||||
|
||||
echo "delete backup"
|
||||
pg_rman -p $TEST_PGPORT delete --debug -d postgres > $BASE_PATH/results/log_delete1 2>&1
|
||||
pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show1 2>&1
|
||||
echo "# of deleted backups"
|
||||
grep -c DELETED $BASE_PATH/results/log_show1
|
||||
pg_rman -p $TEST_PGPORT delete `date "+%Y-%m-%d %T"` --debug -d postgres > $BASE_PATH/results/log_delete2 2>&1
|
||||
pg_rman -p $TEST_PGPORT show `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show2 2>&1
|
||||
echo "# of deleted backups"
|
||||
grep -c DELETED $BASE_PATH/results/log_show2
|
||||
pg_rman -p $TEST_PGPORT show timeline `date +%Y` -a --verbose -d postgres > $BASE_PATH/results/log_show_timeline_4 2>&1
|
||||
|
||||
# cleanup
|
||||
pg_ctl stop -m immediate > /dev/null 2>&1
|
||||
|
2
sql/backup_restore.sql
Normal file
2
sql/backup_restore.sql
Normal file
@ -0,0 +1,2 @@
|
||||
\! sh sql/backup_restore.sh
|
||||
|
4
sql/init.sql
Normal file
4
sql/init.sql
Normal file
@ -0,0 +1,4 @@
|
||||
\! rm -rf results/init_test
|
||||
\! pg_rman init -B results/init_test --quiet;echo $?
|
||||
\! find results/init_test | xargs ls -Fd | sort
|
||||
\! pg_rman init -B results/init_test --quiet;echo $?
|
80
sql/option.sh
Normal file
80
sql/option.sh
Normal file
@ -0,0 +1,80 @@
|
||||
#!/bin/sh
|
||||
|
||||
#============================================================================
|
||||
# This is a test script for option test of pg_rman.
|
||||
#============================================================================
|
||||
|
||||
BASE_PATH=`pwd`
|
||||
|
||||
# Clear environment variables used by pg_rman except $PGDATA.
|
||||
# List of environment variables is defined in catalog.c.
|
||||
unset BACKUP_PATH
|
||||
unset ARCLOG_PATH
|
||||
unset SRVLOG_PATH
|
||||
unset BACKUP_MODE
|
||||
unset COMPRESS_DATA
|
||||
unset KEEP_ARCLOG_DAYS
|
||||
unset KEEP_DATA_GENERATIONS
|
||||
unset KEEP_DATA_DAYS
|
||||
unset KEEP_SRVLOG_FILES
|
||||
unset KEEP_SRVLOG_DAYS
|
||||
|
||||
export PGDATA=$BASE_PATH/results/sample_database
|
||||
|
||||
# Note: not exported
|
||||
BACKUP_PATH=$BASE_PATH/results/sample_backup2
|
||||
|
||||
# Setup backup catalog for backup test.
|
||||
rm -rf $BACKUP_PATH
|
||||
cp -rp data/sample_backup $BACKUP_PATH
|
||||
|
||||
# general option
|
||||
pg_rman --help
|
||||
pg_rman --version
|
||||
|
||||
# backup option
|
||||
# required arguments check
|
||||
pg_rman backup --verbose
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
pg_rman backup --verbose -B $BACKUP_PATH -b f
|
||||
pg_rman backup --verbose -B $BACKUP_PATH -b i
|
||||
pg_rman backup --verbose -B $BACKUP_PATH -b a
|
||||
|
||||
# bad arguments check
|
||||
pg_rman backup --verbose -B $BACKUP_PATH -b bad
|
||||
|
||||
# delete or validate requires DATE
|
||||
pg_rman delete -B $BACKUP_PATH
|
||||
pg_rman validate -B $BACKUP_PATH
|
||||
|
||||
# invalid configuration file check
|
||||
echo " = INFINITE" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "BACKUP_MODE= " > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "BACKUP_MODE = F#S" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "BACKUP_MODE = F #comment A" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "BACKUP_MODE=B" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "COMPRESS_DATA=FOO" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "KEEP_ARCLOG_FILES=YES" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "TIMELINEID=-1" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "BACKUP_TARGETS=F" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
echo "BACKUP_MODE='F''\'\\\F'" > $BACKUP_PATH/pg_rman.ini
|
||||
pg_rman backup --verbose -B $BACKUP_PATH
|
||||
|
||||
# configuration priorityfile check
|
||||
echo "BACKUP_MODE=ENV_PATH" > $BACKUP_PATH/pg_rman.ini
|
||||
mkdir $BACKUP_PATH/conf_path
|
||||
echo "BACKUP_PATH=$BACKUP_PATH/conf_path" > $BACKUP_PATH/pg_rman.conf
|
||||
echo "BACKUP_MODE=CONF_PATH" > $BACKUP_PATH/conf_path/pg_rman.ini
|
||||
mkdir $BACKUP_PATH/comm_path
|
||||
echo "BACKUP_MODE=COMM_PATH" > $BACKUP_PATH/comm_path/pg_rman.ini
|
||||
export BACKUP_PATH=$BACKUP_PATH
|
||||
pg_rman backup --verbose
|
1
sql/option.sql
Normal file
1
sql/option.sql
Normal file
@ -0,0 +1 @@
|
||||
\! sh sql/option.sh
|
8
sql/show_validate.sql
Normal file
8
sql/show_validate.sql
Normal file
@ -0,0 +1,8 @@
|
||||
-- test show command
|
||||
\! rm -rf results/sample_backup
|
||||
\! cp -rp data/sample_backup results/sample_backup
|
||||
\! pg_rman show -B results/sample_backup
|
||||
\! pg_rman validate -B results/sample_backup 2009-05-31 17:05:53 --debug
|
||||
\! pg_rman validate -B results/sample_backup 2009-06-01 17:05:53 --debug
|
||||
\! pg_rman show -a -B results/sample_backup
|
||||
\! pg_rman show 2009-06-01 17:05:53 -B results/sample_backup
|
85
util.c
Normal file
85
util.c
Normal file
@ -0,0 +1,85 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* util.c: log messages to log file or stderr, and misc code.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
/*
|
||||
* Convert time_t value to ISO-8601 format string
|
||||
*/
|
||||
void
|
||||
time2iso(char *buf, size_t len, time_t time)
|
||||
{
|
||||
struct tm *tm = localtime(&time);
|
||||
|
||||
if (time == (time_t) 0)
|
||||
strncpy(buf,"****-**-** **:**:**", len);
|
||||
else
|
||||
strftime(buf, len, "%Y-%m-%d %H:%M:%S", tm);
|
||||
}
|
||||
|
||||
const char *
|
||||
status2str(BackupStatus status)
|
||||
{
|
||||
static const char *statusName[] =
|
||||
{
|
||||
"UNKNOWN",
|
||||
"OK",
|
||||
"RUNNING",
|
||||
"ERROR",
|
||||
"DELETING",
|
||||
"DELETED",
|
||||
"DONE",
|
||||
"CORRUPT"
|
||||
};
|
||||
|
||||
if (status < BACKUP_STATUS_INVALID || BACKUP_STATUS_CORRUPT < status)
|
||||
return "UNKNOWN";
|
||||
|
||||
return statusName[status];
|
||||
}
|
||||
|
||||
void
|
||||
remove_trailing_space(char *buf, int comment_mark)
|
||||
{
|
||||
int i;
|
||||
char *last_char = NULL;
|
||||
|
||||
for (i = 0; buf[i]; i++)
|
||||
{
|
||||
if (buf[i] == comment_mark || buf[i] == '\n' || buf[i] == '\r')
|
||||
{
|
||||
buf[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; buf[i]; i++)
|
||||
{
|
||||
if (!isspace(buf[i]))
|
||||
last_char = buf + i;
|
||||
}
|
||||
if (last_char != NULL)
|
||||
*(last_char + 1) = '\0';
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
remove_not_digit(char *buf, size_t len, const char *str)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0, j = 0; str[i] && j < len; i++)
|
||||
{
|
||||
if (!isdigit(str[i]))
|
||||
continue;
|
||||
buf[j++] = str[i];
|
||||
}
|
||||
buf[j] = '\0';
|
||||
}
|
183
validate.c
Normal file
183
validate.c
Normal file
@ -0,0 +1,183 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* validate.c: validate backup files.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
static bool pgBackupValidateFiles(parray *files, const char *root, bool size_only);
|
||||
|
||||
/*
|
||||
* Validate files in the backup and update its status to OK.
|
||||
* If any of files are corrupted, update its stutus to CORRUPT.
|
||||
*/
|
||||
int
|
||||
do_validate(pgBackupRange *range)
|
||||
{
|
||||
int i;
|
||||
parray *backup_list;
|
||||
|
||||
catalog_lock();
|
||||
|
||||
/* get backup list matches given range */
|
||||
backup_list = catalog_get_backup_list(range);
|
||||
parray_qsort(backup_list, pgBackupCompareId);
|
||||
for (i = 0; i < parray_num(backup_list); i++)
|
||||
{
|
||||
pgBackup *backup = (pgBackup *)parray_get(backup_list, i);
|
||||
|
||||
/* Validate completed backups only. */
|
||||
if (backup->status != BACKUP_STATUS_DONE)
|
||||
continue;
|
||||
|
||||
/* validate with CRC value and update status to OK */
|
||||
pgBackupValidate(backup, false);
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
parray_walk(backup_list, pgBackupFree);
|
||||
parray_free(backup_list);
|
||||
|
||||
catalog_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate each files in the backup with its size.
|
||||
*/
|
||||
void
|
||||
pgBackupValidate(pgBackup *backup, bool size_only)
|
||||
{
|
||||
char timestamp[100];
|
||||
char base_path[MAXPGPATH];
|
||||
char path[MAXPGPATH];
|
||||
parray *files;
|
||||
bool corrupted = false;
|
||||
|
||||
time2iso(timestamp, lengthof(timestamp), backup->start_time);
|
||||
elog(INFO, "validate: %s", timestamp);
|
||||
|
||||
if (HAVE_DATABASE(backup))
|
||||
{
|
||||
elog(LOG, "database files...");
|
||||
pgBackupGetPath(backup, base_path, lengthof(base_path), DATABASE_DIR);
|
||||
pgBackupGetPath(backup, path, lengthof(path),
|
||||
DATABASE_FILE_LIST);
|
||||
files = dir_read_file_list(base_path, path);
|
||||
if (!pgBackupValidateFiles(files, base_path, size_only))
|
||||
corrupted = true;
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
}
|
||||
if (HAVE_ARCLOG(backup))
|
||||
{
|
||||
elog(LOG, "archive WAL files...");
|
||||
pgBackupGetPath(backup, base_path, lengthof(base_path), ARCLOG_DIR);
|
||||
pgBackupGetPath(backup, path, lengthof(path), ARCLOG_FILE_LIST);
|
||||
files = dir_read_file_list(base_path, path);
|
||||
if (!pgBackupValidateFiles(files, base_path, size_only))
|
||||
corrupted = true;
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
}
|
||||
if (backup->with_serverlog)
|
||||
{
|
||||
elog(LOG, "server log files...");
|
||||
pgBackupGetPath(backup, base_path, lengthof(base_path), SRVLOG_DIR);
|
||||
pgBackupGetPath(backup, path, lengthof(path), SRVLOG_FILE_LIST);
|
||||
files = dir_read_file_list(base_path, path);
|
||||
if (!pgBackupValidateFiles(files, base_path, size_only))
|
||||
corrupted = true;
|
||||
parray_walk(files, pgFileFree);
|
||||
parray_free(files);
|
||||
}
|
||||
|
||||
/* update status to OK */
|
||||
if (corrupted)
|
||||
backup->status = BACKUP_STATUS_CORRUPT;
|
||||
else
|
||||
backup->status = BACKUP_STATUS_OK;
|
||||
pgBackupWriteIni(backup);
|
||||
|
||||
if (corrupted)
|
||||
elog(WARNING, "backup %s is corrupted", timestamp);
|
||||
else
|
||||
elog(LOG, "backup %s is valid", timestamp);
|
||||
}
|
||||
|
||||
static const char *
|
||||
get_relative_path(const char *path, const char *root)
|
||||
{
|
||||
size_t rootlen = strlen(root);
|
||||
if (strncmp(path, root, rootlen) == 0 && path[rootlen] == '/')
|
||||
return path + rootlen + 1;
|
||||
else
|
||||
return path;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate files in the backup with size or CRC.
|
||||
*/
|
||||
static bool
|
||||
pgBackupValidateFiles(parray *files, const char *root, bool size_only)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < parray_num(files); i++)
|
||||
{
|
||||
struct stat st;
|
||||
pgFile *file = (pgFile *) parray_get(files, i);
|
||||
|
||||
if (interrupted)
|
||||
elog(ERROR_INTERRUPTED, _("interrupted during validate"));
|
||||
|
||||
/* skipped backup while incremental backup */
|
||||
if (file->write_size == BYTES_INVALID || !S_ISREG(file->mode))
|
||||
continue;
|
||||
|
||||
/* print progress */
|
||||
elog(LOG, _("(%d/%lu) %s"), i + 1, (unsigned long) parray_num(files),
|
||||
get_relative_path(file->path, root));
|
||||
|
||||
/* always validate file size */
|
||||
if (stat(file->path, &st) == -1)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
elog(WARNING, _("backup file \"%s\" vanished"), file->path);
|
||||
else
|
||||
elog(ERROR_SYSTEM, _("can't stat backup file \"%s\": %s"),
|
||||
get_relative_path(file->path, root), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (file->write_size != st.st_size)
|
||||
{
|
||||
elog(WARNING, _("size of backup file \"%s\" must be %lu but %lu"),
|
||||
get_relative_path(file->path, root),
|
||||
(unsigned long) file->write_size,
|
||||
(unsigned long) st.st_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* validate CRC too */
|
||||
if (!size_only)
|
||||
{
|
||||
pg_crc32 crc;
|
||||
crc = pgFileGetCRC(file);
|
||||
if (crc != file->crc)
|
||||
{
|
||||
elog(WARNING, _("CRC of backup file \"%s\" must be %X but %X"),
|
||||
get_relative_path(file->path, root), file->crc, crc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
91
xlog.c
Normal file
91
xlog.c
Normal file
@ -0,0 +1,91 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* xlog.c: Parse WAL files.
|
||||
*
|
||||
* Copyright (c) 2009, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "pg_rman.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if PG_VERSION_NUM >= 80400
|
||||
typedef unsigned long Datum;
|
||||
typedef struct MemoryContextData *MemoryContext;
|
||||
#endif
|
||||
|
||||
#include "access/xlog_internal.h"
|
||||
|
||||
/*
|
||||
* Return whether the file is a WAL segment or not.
|
||||
* based on ValidXLOGHeader() in src/backend/access/transam/xlog.c.
|
||||
*/
|
||||
bool
|
||||
xlog_is_complete_wal(const pgFile *file)
|
||||
{
|
||||
FILE *fp;
|
||||
char page[XLOG_BLCKSZ];
|
||||
XLogPageHeader header = (XLogPageHeader) page;
|
||||
XLogLongPageHeader lheader = (XLogLongPageHeader) page;
|
||||
|
||||
fp = fopen(file->path, "r");
|
||||
if (!fp)
|
||||
return false;
|
||||
if (fread(page, 1, sizeof(page), fp) != XLOG_BLCKSZ)
|
||||
{
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
/* check header */
|
||||
if (header->xlp_magic != XLOG_PAGE_MAGIC)
|
||||
return false;
|
||||
if ((header->xlp_info & ~XLP_ALL_FLAGS) != 0)
|
||||
return false;
|
||||
|
||||
if (header->xlp_info & XLP_LONG_HEADER)
|
||||
{
|
||||
if (lheader->xlp_seg_size != XLogSegSize)
|
||||
return false;
|
||||
|
||||
/* compressed WAL (with lesslog) has 0 in lheader->xlp_xlog_blcksz. */
|
||||
if (lheader->xlp_xlog_blcksz != XLOG_BLCKSZ &&
|
||||
lheader->xlp_xlog_blcksz != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* check size (actual file size, not backup file size) */
|
||||
if (lheader->xlp_xlog_blcksz == XLOG_BLCKSZ && file->size != XLogSegSize)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
xlog_logfname2lsn(const char *logfname, XLogRecPtr *lsn)
|
||||
{
|
||||
uint32 tli;
|
||||
if (sscanf(logfname, "%08X%08X%08X",
|
||||
&tli, &lsn->xlogid, &lsn->xrecoff) != 3)
|
||||
return false;
|
||||
|
||||
lsn->xrecoff *= XLogSegSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* based on XLogFileName() in xlog_internal.h
|
||||
*/
|
||||
void
|
||||
xlog_fname(char *fname, size_t len, TimeLineID tli, XLogRecPtr *lsn)
|
||||
{
|
||||
snprintf(fname, len, "%08X%08X%08X", tli,
|
||||
lsn->xlogid, lsn->xrecoff / XLogSegSize);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user