1
0
mirror of https://github.com/postgrespro/pg_probackup.git synced 2025-09-16 09:26:30 +02:00

release pg_rman-1.2.0.

git-svn-id: http://pg-rman.googlecode.com/svn/trunk@38 182aca00-e38e-11de-a668-6fd11605f5ce
This commit is contained in:
katsumata.tomonari@oss.ntt.co.jp
2011-02-07 01:57:11 +00:00
parent 686a5b0cf2
commit 566c47a6fc
30 changed files with 12185 additions and 8304 deletions

View File

@@ -24,7 +24,7 @@ OBJS = $(SRCS:.c=.o)
PG_CPPFLAGS = -I$(libpq_srcdir)
PG_LIBS = $(libpq_pgport)
REGRESS = option init show_validate backup_restore
REGRESS = option init show_validate backup_restore snapshot
ifdef USE_PGXS
PG_CONFIG = pg_config

64
SPECS/pg_rman90.spec Normal file
View File

@@ -0,0 +1,64 @@
# SPEC file for pg_rman
# Copyright(C) 2009-2010 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
%define _pgdir /usr/pgsql-9.0
%define _bindir %{_pgdir}/bin
%define _libdir %{_pgdir}/lib
%define _datadir %{_pgdir}/share
## Set general information for pg_rman.
Summary: Backup and Recovery Tool for PostgreSQL
Name: pg_rman
Version: 1.2.0
Release: 1%{?dist}
License: BSD
Group: Applications/Databases
Source0: %{name}-%{version}.tar.gz
URL: http://code.google.com/p/pg-rman/
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n)
Vendor: NIPPON TELEGRAPH AND TELEPHONE CORPORATION
## We use postgresql-devel package
BuildRequires: postgresql90-devel
Requires: postgresql90-libs
## Description for "pg_rman"
%description
pg_rman manages backup and recovery of PostgreSQL.
pg_rman has the features below:
-Takes a backup while database including tablespaces with just one command.
-Can recovery from backup with just one command.
-Supports incremental backup and compression of backup files so that it takes less disk spaces.
-Manages backup generations and shows a catalog of the backups.
## pre work for build pg_rman
%prep
%setup -q -n %{name}-%{version}
## Set variables for build environment
%build
USE_PGXS=1 make %{?_smp_mflags}
## Set variables for install
%install
rm -rf %{buildroot}
USE_PGXS=1 make %{?_smp_mflags} install
install -d %{buildroot}%{_bindir}
install -m 755 pg_rman %{buildroot}%{_bindir}/pg_rman
%clean
rm -rf %{buildroot}
%files
%defattr(755,root,root)
%{_bindir}/pg_rman
# History of pg_rman.
%changelog
* Wed Nov 10 2010 - NTT OSS Center <tomonari.katsumata@oss.ntt.co.jp> 1.2.0-1
* Wed Dec 9 2009 - NTT OSS Center <itagaki.takahiro@oss.ntt.co.jp> 1.1.1-1
- Initial cut for 1.1.1

2783
backup.c

File diff suppressed because it is too large Load Diff

1218
catalog.c

File diff suppressed because it is too large Load Diff

312
clean.c Normal file
View File

@@ -0,0 +1,312 @@
/*-------------------------------------------------------------------------
*
* clean.c: cleanup backup files.
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include <dirent.h>
#include <unistd.h>
static void clean_backup(Database db, pgBackup *backup);
#define CLEAN_MASK (BACKUP_MASK(BACKUP_ERROR) | BACKUP_MASK(BACKUP_BAD))
void
do_clean(int keep_data_generations,
int keep_data_days,
int keep_srvlog_files,
int keep_srvlog_days)
{
Database db;
List *backups;
ListCell *cell;
db = db_open();
backups = db_list_backups(db, make_range(0, NULL), CLEAN_MASK);
foreach (cell, backups)
clean_backup(db, lfirst(cell));
db_close(db);
list_free_deep(backups);
}
/*
* Delete files of the backup and update the status to DELETED.
*/
static void
clean_backup(Database db, pgBackup *backup)
{
char datetime[DATESTRLEN];
char path[MAXPGPATH];
elog(INFO, "clean: %s", date2str(datetime, backup->start_time));
/*
* update the status to BAD before the actual deletion because abort
* during deletion could leave corrupted backup files.
*/
if (backup->status != BACKUP_BAD)
{
backup->status = BACKUP_BAD;
db_update_status(db, backup, NIL);
}
/* remove data files. */
make_backup_path(path, backup->start_time);
remove_file(path);
/* update the status to DELETED */
backup->status = BACKUP_DELETED;
db_update_status(db, backup, NIL);
}
#if 0
/* that are older than KEEP_xxx_DAYS and have more generations
* than KEEP_xxx_FILES.
*/
int i;
parray *backup_list;
int backup_num;
time_t days_threshold = current.start_time - (keep_days * 60 * 60 * 24);
/* cleanup 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);
/*
* Remove backup files
*/
void
clean_backup(int keep_generations, int keep_days)
{
backup_num = 0;
/* find cleanup 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 verify full backup was found, we can cleanup the backup
* that is older than it
*/
if (backup->backup_mode >= BACKUP_MODE_FULL &&
backup->status == BACKUP_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 threshold and
* there are enough generations of full backups, cleanup the backup.
*/
if (backup->start_time >= days_threshold)
{
elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__,
backup->start_time, days_threshold);
continue;
}
}
}
static void delete_old_files(const char *root, parray *files, int keep_files,
int keep_days, int server_version, bool is_arclog);
static void delete_arclog_link(void);
static void delete_online_wal_backup(void);
/*
* Delete files modified before than KEEP_xxx_DAYS or more than KEEP_xxx_FILES
* of newer files exist.
*/
static void
delete_old_files(const char *root,
parray *files,
int keep_files,
int keep_days,
int server_version,
bool is_arclog)
{
int i;
int j;
int file_num = 0;
time_t days_threshold = start_time - (keep_days * 60 * 60 * 24);
if (verbose)
{
char files_str[100];
char days_str[100];
if (keep_files == KEEP_INFINITE)
strncpy(files_str, "INFINITE", lengthof(files_str));
else
snprintf(files_str, lengthof(files_str), "%d", keep_files);
if (keep_days == KEEP_INFINITE)
strncpy(days_str, "INFINITE", lengthof(days_str));
else
snprintf(days_str, lengthof(days_str), "%d", keep_days);
printf("cleanup old files from \"%s\" (files=%s, days=%s)\n",
root, files_str, days_str);
}
/* cleanup files which satisfy both conditions */
if (keep_files == KEEP_INFINITE || keep_days == KEEP_INFINITE)
{
elog(LOG, "%s() infinite", __FUNCTION__);
return;
}
parray_qsort(files, pgFileCompareMtime);
for (i = parray_num(files) - 1; i >= 0; i--)
{
pgFile *file = (pgFile *) parray_get(files, i);
elog(LOG, "%s() %s", __FUNCTION__, file->path);
/* Delete completed WALs only. */
if (is_arclog && !xlog_completed(file, server_version))
{
elog(LOG, "%s() not complete WAL", __FUNCTION__);
continue;
}
file_num++;
/*
* If the mtime of the file is older than the threshold and there are
* enough number of files newer than the files, cleanup the file.
*/
if (file->mtime >= days_threshold)
{
elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__,
file->mtime, days_threshold);
continue;
}
elog(LOG, "%s() %lu is older than %lu", __FUNCTION__,
file->mtime, days_threshold);
if (file_num <= keep_files)
{
elog(LOG, "%s() newer files are only %d", __FUNCTION__, file_num);
continue;
}
}
}
static void
delete_online_wal_backup(void)
{
int i;
parray *files = parray_new();
char work_path[MAXPGPATH];
if (verbose)
{
printf("========================================\n"));
printf("cleanup online WAL backup\n"));
}
snprintf(work_path, lengthof(work_path), "%s/%s/%s", backup_path,
RESTORE_WORK_DIR, PG_XLOG_DIR);
/* don't cleanup root dir */
files = pgFileEnum(work_path, NULL, true, false);
if (parray_num(files) == 0)
{
parray_free(files);
return;
}
parray_qsort(files, pgFileComparePathDesc); /* cleanup from leaf */
for (i = 0; i < parray_num(files); i++)
{
pgFile *file = (pgFile *) parray_get(files, i);
if (verbose)
printf("cleanup \"%s\"\n", file->path);
if (!check)
pgFileDelete(file);
}
parray_walk(files, pgFile_free);
parray_free(files);
}
/*
* Remove symbolic links point archived WAL in backup catalog.
*/
static void
delete_arclog_link(void)
{
int i;
parray *files = parray_new();
if (verbose)
{
printf("========================================\n"));
printf("cleanup symbolic link in archive directory\n"));
}
files = pgFileEnum(arclog_path, NULL, false, false);
for (i = 0; i < parray_num(files); i++)
{
pgFile *file = (pgFile *) parray_get(files, i);
if (!S_ISLNK(file->mode))
continue;
if (verbose)
printf("cleanup \"%s\"\n", file->path);
if (!check && remove(file->path) == -1)
elog(ERROR_SYSTEM, "could not remove link \"%s\": %s", file->path,
strerror(errno));
}
parray_walk(files, pgFile_free);
parray_free(files);
}
/*
* Delete old files (archived WAL and serverlog) after update of status.
*/
if (HAVE_ARCLOG(&current))
delete_old_files(arclog_path, files_arclog, keep_arclog_files,
keep_arclog_days, server_version, true);
/* Delete old backup files after all backup operation. */
clean_backup(keep_data_generations, keep_data_days);
/*
* If this backup is full backup, cleanup backup of online WAL.
* Note that sereverlog files which were backed up during first restoration
* don't be cleanup.
* Also cleanup symbolic link in the archive directory.
*/
if (backup_mode == BACKUP_MODE_FULL)
{
delete_online_wal_backup();
delete_arclog_link();
}
#endif

1980
data.c

File diff suppressed because it is too large Load Diff

729
db.c Normal file
View File

@@ -0,0 +1,729 @@
/*-------------------------------------------------------------------------
*
* db.c: SQLite3 access module
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include <sqlite3.h>
#include <time.h>
static const char *DB_CREATE_SQL[] =
{
"CREATE TABLE backup (\n"
" id integer,\n"
" status integer,\n"
" mode integer,\n"
" start_time integer,\n"
" stop_time integer,\n"
" timeline integer,\n"
" start_xlog integer,\n"
" stop_xlog integer,\n"
" server_size integer,\n"
" dbfile_size integer,\n"
" arclog_size integer,\n"
" PRIMARY KEY (id)\n"
")",
"CREATE TABLE " DBFILE " (\n"
" id integer,\n"
" name text,\n"
" mtime integer,\n"
" size integer,\n"
" mode integer,\n"
" flags integer,\n"
" crc integer,\n"
" PRIMARY KEY (id, name)\n"
")",
"CREATE TABLE " ARCLOG " (\n"
" name text,\n"
" size integer,\n"
" flags integer,\n"
" crc integer,\n"
" PRIMARY KEY (name)\n"
")",
NULL
};
static Database open_internal(int flags);
static void exec(Database db, const char *query);
static sqlite3_stmt *prepare(Database db, const char *query);
static void bind_int32(sqlite3_stmt *stmt, int n, int32 value);
static void bind_int64(sqlite3_stmt *stmt, int n, int64 value);
static void bind_size(sqlite3_stmt *stmt, int n, int64 value);
static void bind_text(sqlite3_stmt *stmt, int n, const char *value);
static void bind_xlog(sqlite3_stmt *stmt, int n, XLogName value);
static XLogName column_xlog(sqlite3_stmt *stmt, int n, TimeLineID tli);
static void step(sqlite3_stmt *stmt, int expected_code);
static void insert_dbfiles(Database db, int64 id, List *files);
static void insert_arclogs(Database db, List *files);
void
db_create(void)
{
Database db;
int i;
db = open_internal(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
exec(db, "BEGIN EXCLUSIVE TRANSACTION");
for (i = 0; DB_CREATE_SQL[i]; i++)
exec(db, DB_CREATE_SQL[i]);
exec(db, "COMMIT");
sqlite3_close(db);
}
Database
db_open(void)
{
return open_internal(SQLITE_OPEN_READWRITE);
}
void
db_close(Database db)
{
sqlite3_close(db);
}
#define AVAIL_MASK (BACKUP_MASK(BACKUP_DONE) | BACKUP_MASK(BACKUP_OK))
pgBackup *
db_start_backup(Database db, BackupMode mode)
{
pgBackup *backup;
sqlite3_stmt *stmt;
TimeLineID tli = pgControlFile.checkPointCopy.ThisTimeLineID;
int i;
Assert(tli != 0);
/* start transaction with an exclusive lock */
exec(db, "BEGIN EXCLUSIVE TRANSACTION");
/* initialize backup status */
backup = pgut_new(pgBackup);
memset(backup, 0, sizeof(pgBackup));
backup->status = BACKUP_ERROR;
backup->mode = mode;
backup->start_time = time(NULL);
backup->server_size = -1;
backup->dbfile_size = -1;
backup->arclog_size = -1;
/* retrieve a new id */
stmt = prepare(db, "SELECT coalesce(max(id) + 1, 1) FROM backup");
step(stmt, SQLITE_ROW);
backup->id = sqlite3_column_int64(stmt, 0);
sqlite3_finalize(stmt);
/* retrieve a previous backup */
stmt = prepare(db,
"SELECT max(stop_xlog) FROM backup "
"WHERE id < ? AND timeline = ? AND ((1 << status) & ?) <> 0 ");
i = 0;
bind_int64(stmt, ++i, backup->id);
bind_int64(stmt, ++i, tli);
bind_int32(stmt, ++i, AVAIL_MASK);
step(stmt, SQLITE_ROW);
backup->start_xlog = column_xlog(stmt, 0, tli);
if (backup->start_xlog.tli == 0)
{
if (backup->mode < MODE_FULL)
elog(INFO, "previous full backup not found. do a full backup instead");
backup->mode = MODE_FULL;
}
else
{
/* goto the next segment */
backup->start_xlog.seg++;
}
sqlite3_finalize(stmt);
/* insert a backup row with 'ERROR' status */
stmt = prepare(db,
"INSERT INTO backup(id, status, mode, start_time) "
"VALUES(?, ?, ?, ?)");
i = 0;
bind_int64(stmt, ++i, backup->id);
bind_int32(stmt, ++i, backup->status);
bind_int32(stmt, ++i, backup->mode);
bind_int64(stmt, ++i, backup->start_time);
step(stmt, SQLITE_DONE);
sqlite3_finalize(stmt);
/* ok, will start file copy */
exec(db, "COMMIT");
return backup;
}
void
db_stop_backup(Database db,
pgBackup *backup,
List *dbfiles,
List *arclogs)
{
sqlite3_stmt *stmt;
int i;
if (backup->start_xlog.tli != backup->stop_xlog.tli)
elog(ERROR, "invalid timeline");
/* start transaction with an exclusive lock */
exec(db, "BEGIN EXCLUSIVE TRANSACTION");
insert_dbfiles(db, backup->id, dbfiles);
insert_arclogs(db, arclogs);
backup->stop_time = time(NULL);
backup->status = BACKUP_DONE;
/* update a backup status to 'DONE' */
stmt = prepare(db,
"UPDATE backup SET "
"status = ?, mode = ?, stop_time = ?, "
"timeline = ?, start_xlog = ?, stop_xlog = ?, "
"server_size = ?, dbfile_size = ?, arclog_size = ? "
"WHERE id = ?");
i = 0;
bind_int32(stmt, ++i, backup->status);
bind_int32(stmt, ++i, backup->mode);
bind_int64(stmt, ++i, backup->stop_time);
bind_int64(stmt, ++i, backup->start_xlog.tli);
bind_xlog(stmt, ++i, backup->start_xlog);
bind_xlog(stmt, ++i, backup->stop_xlog);
bind_size(stmt, ++i, backup->server_size);
bind_size(stmt, ++i, backup->dbfile_size);
bind_size(stmt, ++i, backup->arclog_size);
bind_int64(stmt, ++i, backup->id);
step(stmt, SQLITE_DONE);
sqlite3_finalize(stmt);
exec(db, "COMMIT");
}
/* insert dbfiles */
static void
insert_dbfiles(Database db, int64 id, List *files)
{
ListCell *cell;
sqlite3_stmt *stmt;
Assert(db);
if (files == NIL)
return;
stmt = prepare(db, "INSERT INTO " DBFILE " VALUES(?, ?, ?, ?, ?, ?, ?)");
foreach(cell, files)
{
const pgFile *file = lfirst(cell);
int i;
if (file->mode == MISSING_FILE)
continue;
i = 0;
bind_int64(stmt, ++i, id);
bind_text(stmt, ++i, file->name);
bind_int64(stmt, ++i, file->mtime);
bind_size(stmt, ++i, file->size);
bind_int32(stmt, ++i, file->mode);
bind_int32(stmt, ++i, (int32) file->flags);
bind_int32(stmt, ++i, (int32) file->crc);
// bind_text(stmt, ++i, file->linked);
step(stmt, SQLITE_DONE);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
}
/* insert dbfiles */
static void
insert_arclogs(Database db, List *files)
{
ListCell *cell;
sqlite3_stmt *stmt;
Assert(db);
if (files == NIL)
return;
stmt = prepare(db, "INSERT INTO " ARCLOG " VALUES(?, ?, ?, ?)");
foreach(cell, files)
{
const pgFile *file = lfirst(cell);
int i;
if (file->mode == MISSING_FILE)
continue;
i = 0;
bind_text(stmt, ++i, file->name);
bind_size(stmt, ++i, file->size);
bind_int32(stmt, ++i, (int32) file->flags);
bind_int32(stmt, ++i, (int32) file->crc);
step(stmt, SQLITE_DONE);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
}
void
db_check_modified(Database db, List *files)
{
ListCell *cell;
sqlite3_stmt *stmt = NULL;
foreach (cell, files)
{
pgFile *file = lfirst(cell);
if (S_ISREG(file->mode) && (file->flags & PGFILE_PARTIAL) != 0)
{
time_t mtime;
if (stmt == NULL)
stmt = prepare(db,
"SELECT max(mtime) FROM " DBFILE " WHERE name = ?");
bind_text(stmt, 1, file->name);
step(stmt, SQLITE_ROW);
/* NULL is converted to 0 */
mtime = (time_t) sqlite3_column_int64(stmt, 0);
if (file->mtime == mtime)
file->flags |= PGFILE_UNMODIFIED;
sqlite3_reset(stmt);
}
}
if (stmt)
sqlite3_finalize(stmt);
}
#define SELECT_FROM_BACKUP \
"SELECT id, status, mode, start_time, stop_time, " \
"timeline, start_xlog, stop_xlog, " \
"coalesce(server_size, -1), " \
"coalesce(dbfile_size, -1), " \
"coalesce(arclog_size, -1) " \
"FROM backup "
static List *
list_backups(sqlite3_stmt *stmt)
{
List *backups = NIL;
TimeLineID timeline;
while (sqlite3_step(stmt) == SQLITE_ROW)
{
pgBackup *backup = pgut_new(pgBackup);
int i;
memset(backup, 0, sizeof(pgBackup));
i = 0;
backup->id = sqlite3_column_int64(stmt, i++);
backup->status = sqlite3_column_int64(stmt, i++);
backup->mode = sqlite3_column_int(stmt, i++);
backup->start_time = sqlite3_column_int64(stmt, i++);
backup->stop_time = sqlite3_column_int64(stmt, i++);
timeline = (TimeLineID) sqlite3_column_int64(stmt, i++);
backup->start_xlog = column_xlog(stmt, i++, timeline);
backup->stop_xlog = column_xlog(stmt, i++, timeline);
backup->server_size = sqlite3_column_int64(stmt, i++);
backup->dbfile_size = sqlite3_column_int64(stmt, i++);
backup->arclog_size = sqlite3_column_int64(stmt, i++);
backups = lappend(backups, backup);
}
sqlite3_finalize(stmt);
return backups;
}
List *
db_list_backups(Database db, pgRange range, bits32 mask)
{
sqlite3_stmt *stmt;
int i;
stmt = prepare(db,
SELECT_FROM_BACKUP
"WHERE ? <= start_time AND start_time < ? "
"AND ((1 << status) & ?) <> 0 "
"ORDER BY id");
i = 0;
bind_int64(stmt, ++i, range.begin);
bind_int64(stmt, ++i, range.end);
bind_int32(stmt, ++i, mask);
return list_backups(stmt);
}
/* return a full backup and successive incremental backups to recover to tli */
List *
db_list_backups_for_restore(Database db,
time_t target_time,
TimeLineID target_tli)
{
sqlite3_stmt *stmt;
int i;
#if 0
/* is the backup is necessary for restore to target timeline ? */
if (!satisfy_timeline(timelines, backup))
continue;
timelines = readTimeLineHistory(target_tli);
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_xlog, timeline->end))
return true;
}
return false;
}
#endif
stmt = prepare(db,
SELECT_FROM_BACKUP
"WHERE ((1 << status) & ?) <> 0 AND start_time < ? "
"AND id >= (SELECT max(id) FROM backup "
"WHERE ((1 << status) & ?) <> 0 AND start_time < ? "
"AND mode = ? AND ? IN (0, timeline) ) "
"ORDER BY id");
i = 0;
bind_int32(stmt, ++i, AVAIL_MASK);
bind_int64(stmt, ++i, target_time);
bind_int32(stmt, ++i, AVAIL_MASK);
bind_int64(stmt, ++i, target_time);
bind_int32(stmt, ++i, MODE_FULL);
bind_int64(stmt, ++i, target_tli);
return list_backups(stmt);
}
List *
db_list_dbfiles(Database db, const pgBackup *backup)
{
List *files = NIL;
sqlite3_stmt *stmt;
int i;
char root[MAXPGPATH];
make_backup_path(root, backup->start_time);
stmt = prepare(db,
"SELECT name, mtime, size, mode, flags, crc FROM " DBFILE
" WHERE id = ? ORDER BY name");
i = 0;
bind_int64(stmt, ++i, backup->id);
while (sqlite3_step(stmt) == SQLITE_ROW)
{
pgFile *file;
i = 0;
file = pgFile_new((const char *) sqlite3_column_text(stmt, i++));
file->mtime = (time_t) sqlite3_column_int64(stmt, i++);
file->size = sqlite3_column_int64(stmt, i++);
file->mode = (mode_t) sqlite3_column_int(stmt, i++);
file->flags = sqlite3_column_int(stmt, i++);
file->crc = (pg_crc32) sqlite3_column_int(stmt, i++);
files = lappend(files, file);
}
sqlite3_finalize(stmt);
return files;
}
static pgFile *
newMissingXLog(XLogName xlog)
{
pgFile *file;
char name[XLOGNAMELEN];
xlog_name(name, xlog);
file = pgFile_new(name);
file->size = 0;
file->mode = S_IFREG;
file->flags = 0;
file->crc = 0;
return file;
}
/*
* list arclogs required by backup, or list all if backup is NULL.
* The result includes all of required xlog files whenbackup specified even if
* the xlog file is not in the catalog.
*/
List *
db_list_arclogs(Database db, const pgBackup *backup)
{
List *files = NIL;
sqlite3_stmt *stmt;
int i;
char lo[XLOGNAMELEN];
char hi[XLOGNAMELEN];
XLogName next;
XLogName stop;
if (backup)
{
next = backup->start_xlog;
stop = backup->stop_xlog;
}
else
{
next.tli = next.log = next.seg = 0x00000000;
stop.tli = stop.log = stop.seg = 0xFFFFFFFF;
}
xlog_name(lo, backup->start_xlog);
xlog_name(hi, xlog_next(backup->stop_xlog));
stmt = prepare(db,
"SELECT name, size, flags, crc "
"FROM " ARCLOG " WHERE ? <= name AND name < ? "
"ORDER BY name");
i = 0;
bind_text(stmt, ++i, lo);
bind_text(stmt, ++i, hi);
while (sqlite3_step(stmt) == SQLITE_ROW)
{
pgFile *file;
const char *name;
i = 0;
name = (const char *) sqlite3_column_text(stmt, i++);
/* add missing arclogs */
if (strlen(name) == 24 &&
strspn(name, "0123456789ABCDEF") == 24)
{
XLogName xlog = parse_xlogname(name);
while (xlog.tli == next.tli && (xlog.log <= next.log || xlog.seg < xlog.seg))
{
files = lappend(files, newMissingXLog(xlog));
xlog = xlog_next(xlog);
}
next = xlog_next(xlog);
}
file = pgFile_new(name);
file->size = sqlite3_column_int64(stmt, i++);
file->mode = S_IFREG;
file->flags = sqlite3_column_int(stmt, i++);
file->crc = (pg_crc32) sqlite3_column_int(stmt, i++);
files = lappend(files, file);
}
sqlite3_finalize(stmt);
/* add missing arclogs */
while (next.tli == stop.tli && (next.log <= stop.log || next.seg < stop.seg))
{
files = lappend(files, newMissingXLog(next));
next = xlog_next(next);
}
return files;
}
void
db_update_status(Database db, const pgBackup *backup, List *arglogs)
{
sqlite3_stmt *stmt;
ListCell *cell;
int i;
/* start transaction with an exclusive lock */
exec(db, "BEGIN EXCLUSIVE TRANSACTION");
if (backup->status == BACKUP_DELETED)
{
/* delete files */
stmt = prepare(db, "DELETE FROM " DBFILE " WHERE id = ?");
i = 0;
bind_int64(stmt, ++i, backup->id);
step(stmt, SQLITE_DONE);
sqlite3_finalize(stmt);
/* delete the backup */
stmt = prepare(db, "DELETE FROM backup WHERE id = ?");
i = 0;
bind_int64(stmt, ++i, backup->id);
step(stmt, SQLITE_DONE);
sqlite3_finalize(stmt);
}
else
{
/* update the backup status */
stmt = prepare(db, "UPDATE backup SET status = ? WHERE id = ?");
i = 0;
bind_int32(stmt, ++i, backup->status);
bind_int64(stmt, ++i, backup->id);
step(stmt, SQLITE_DONE);
sqlite3_finalize(stmt);
}
/* update status of archive logs */
stmt = NULL;
foreach (cell, arglogs)
{
const pgFile *file = lfirst(cell);
if (file->flags & PGFILE_VERIFIED)
{
if (stmt == NULL)
stmt = prepare(db,
"UPDATE " ARCLOG " SET flags = ?, crc = ? WHERE name = ?");
i = 0;
bind_int32(stmt, ++i, (int32) file->flags);
bind_int32(stmt, ++i, (int32) file->crc);
bind_text(stmt, ++i, file->name);
step(stmt, SQLITE_DONE);
sqlite3_reset(stmt);
}
}
if (stmt != NULL)
sqlite3_finalize(stmt);
exec(db, "COMMIT");
}
static Database
open_internal(int flags)
{
Database db;
char path[MAXPGPATH];
join_path_components(path, backup_path, PG_RMAN_DATABASE);
if (sqlite3_open_v2(path, &db, flags, NULL) != SQLITE_OK)
elog(ERROR, "could not create database \"%s\": %s",
path, sqlite3_errmsg(db));
return db;
}
static void
exec(Database db, const char *query)
{
char *msg;
if (sqlite3_exec(db, query, NULL, NULL, &msg) != SQLITE_OK)
elog(ERROR, "could not execute query \"%s\": %s", query, msg);
}
static sqlite3_stmt *
prepare(Database db, const char *query)
{
sqlite3_stmt *stmt;
int code;
if ((code = sqlite3_prepare_v2(db, query, -1, &stmt, NULL)) != SQLITE_OK)
elog(ERROR, "could not prepare query \"%s\": %s",
query, sqlite3_errmsg(db));
return stmt;
}
/* for int32, pg_crc32 and mode_t */
static void
bind_int32(sqlite3_stmt *stmt, int n, int32 value)
{
int code = sqlite3_bind_int(stmt, n, value);
if (code != SQLITE_OK)
elog(ERROR, "could not bind a parameter: code %d", code);
}
/* for int64 and time_t */
static void
bind_int64(sqlite3_stmt *stmt, int n, int64 value)
{
int code = sqlite3_bind_int64(stmt, n, value);
if (code != SQLITE_OK)
elog(ERROR, "could not bind a parameter: code %d", code);
}
/* for size_t */
static void
bind_size(sqlite3_stmt *stmt, int n, int64 value)
{
if (value < 0)
sqlite3_bind_null(stmt, n);
else
bind_int64(stmt, n, value);
}
/* for static null-terminated text */
static void
bind_text(sqlite3_stmt *stmt, int n, const char *value)
{
int code = sqlite3_bind_text(stmt, n, value, -1, SQLITE_STATIC);
if (code != SQLITE_OK)
elog(ERROR, "could not bind a parameter: code %d", code);
}
static void
bind_xlog(sqlite3_stmt *stmt, int n, XLogName value)
{
if (value.tli == 0)
sqlite3_bind_null(stmt, n);
else
bind_int64(stmt, n, (((int64) value.log) << 32 | value.seg));
}
/* return 0/0/0 if the input is null */
static XLogName
column_xlog(sqlite3_stmt *stmt, int n, TimeLineID tli)
{
XLogName xlog;
if (tli == 0 || sqlite3_column_type(stmt, n) != SQLITE_INTEGER)
memset(&xlog, 0, sizeof(xlog));
else
{
int64 value = sqlite3_column_int64(stmt, n);
xlog.tli = tli;
xlog.log = (uint32) ((value >> 32) & 0xFFFFFFFF);
xlog.seg = (uint32) (value & 0xFFFFFFFF);
}
return xlog;
}
static void
step(sqlite3_stmt *stmt, int expected_code)
{
int code = sqlite3_step(stmt);
if (code != expected_code)
elog(ERROR, "unexpected result in step: code %d", code);
}

470
delete.c
View File

@@ -1,235 +1,235 @@
/*-------------------------------------------------------------------------
*
* delete.c: delete backup files.
*
* Copyright (c) 2009-2010, 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_threshold = 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 threshold and
* there are enough generations of full backups, delete the backup.
*/
if (backup->start_time >= days_threshold)
{
elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__,
backup->start_time, days_threshold);
continue;
}
elog(LOG, "%s() %lu is older than %lu", __FUNCTION__,
backup->start_time, days_threshold);
/* 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;
}
/*-------------------------------------------------------------------------
*
* delete.c: delete backup files.
*
* Copyright (c) 2009-2010, 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_threshold = 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 threshold and
* there are enough generations of full backups, delete the backup.
*/
if (backup->start_time >= days_threshold)
{
elog(LOG, "%s() %lu is not older than %lu", __FUNCTION__,
backup->start_time, days_threshold);
continue;
}
elog(LOG, "%s() %lu is older than %lu", __FUNCTION__,
backup->start_time, days_threshold);
/* 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;
}

1154
dir.c

File diff suppressed because it is too large Load Diff

705
file.c Normal file
View File

@@ -0,0 +1,705 @@
/*-------------------------------------------------------------------------
*
* file.c:
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include "storage/block.h"
#include "storage/bufpage.h"
#ifdef HAVE_LIBZ
#include <zlib.h>
#endif
/* ----------------------------------------------------------------
* Reader and Writer
* ----------------------------------------------------------------
*/
typedef struct Reader Reader;
typedef size_t (*ReaderClose)(Reader *self);
typedef size_t (*ReaderRead)(Reader *self, void *buf, size_t len);
struct Reader
{
ReaderClose close; /* close and returns physical read size */
ReaderRead read;
};
typedef struct Writer Writer;
typedef size_t (*WriterClose)(Writer *self, pg_crc32 *crc);
typedef void (*WriterWrite)(Writer *self, const void *buf, size_t len);
struct Writer
{
WriterClose close; /* close and returns physical written size */
WriterWrite write;
};
static Reader *FileReader(const char *path);
static Writer *FileWriter(const char *path);
static Reader *ZlibReader(Reader *inner);
static Writer *ZlibWriter(Writer *inner);
static Reader *DataReader(Reader *inner);
static Reader *BackupReader(Reader *inner);
#define inner_read(self, buf, len) \
((self)->inner->read((self)->inner, (buf), (len)))
#define inner_write(self, buf, len) \
((self)->inner->write((self)->inner, (buf), (len)))
/* ----------------------------------------------------------------
* PostgreSQL data files
* ----------------------------------------------------------------
*/
#define PG_PAGE_LAYOUT_VERSION_v81 3 /* 8.1 - 8.2 */
#define PG_PAGE_LAYOUT_VERSION_v83 4 /* 8.3 - */
/* 80000 <= PG_VERSION_NUM < 80300 */
typedef struct PageHeaderData_v80
{
XLogRecPtr pd_lsn;
TimeLineID pd_tli;
LocationIndex pd_lower;
LocationIndex pd_upper;
LocationIndex pd_special;
uint16 pd_pagesize_version;
ItemIdData pd_linp[1];
} PageHeaderData_v80;
#define PageGetPageSize_v80(page) \
((Size) ((page)->pd_pagesize_version & (uint16) 0xFF00))
#define PageGetPageLayoutVersion_v80(page) \
((page)->pd_pagesize_version & 0x00FF)
#define SizeOfPageHeaderData_v80 (offsetof(PageHeaderData_v80, pd_linp))
/* 80300 <= PG_VERSION_NUM */
typedef struct PageHeaderData_v83
{
XLogRecPtr pd_lsn;
uint16 pd_tli;
uint16 pd_flags;
LocationIndex pd_lower;
LocationIndex pd_upper;
LocationIndex pd_special;
uint16 pd_pagesize_version;
TransactionId pd_prune_xid;
ItemIdData pd_linp[1];
} PageHeaderData_v83;
#define PageGetPageSize_v83(page) \
((Size) ((page)->pd_pagesize_version & (uint16) 0xFF00))
#define PageGetPageLayoutVersion_v83(page) \
((page)->pd_pagesize_version & 0x00FF)
#define SizeOfPageHeaderData_v83 (offsetof(PageHeaderData_v83, pd_linp))
#define PD_VALID_FLAG_BITS_v83 0x0007
typedef union DataPage
{
XLogRecPtr pd_lsn;
PageHeaderData_v80 v80; /* 8.0 - 8.2 */
PageHeaderData_v83 v83; /* 8.3 - */
char data[1];
} DataPage;
static void do_copy(pgFile *file, Reader *in, Writer *out);
/*
* Backup a file.
*/
void
pgFile_backup(pgFile *file, const char *from, const char *to)
{
Reader *in;
Writer *out;
char path[MAXPGPATH];
Assert(file);
Assert(from);
Assert(to);
/* Reader */
join_path_components(path, from, file->name);
if ((in = FileReader(path)) == NULL)
{
file->mode = MISSING_FILE;
return; /* have been deleted, ignore this file */
}
if (file->flags & PGFILE_DATA)
in = DataReader(in);
/* Writer */
join_path_components(path, to, file->name);
out = FileWriter(path);
if (file->flags & PGFILE_ZLIB)
out = ZlibWriter(out);
do_copy(file, in, out);
elog(LOG, "backup file: %s (%.2f%% of %lu bytes)",
file->name, 100.0 * file->written_size / file->size,
(unsigned long) file->size);
}
/*
* Restore a file.
*/
void
pgFile_restore(pgFile *file, const char *from, const char *to)
{
Reader *in;
Writer *out;
char path[MAXPGPATH];
Assert(file);
Assert(from);
Assert(to);
/* Reader */
join_path_components(path, from, file->name);
if ((in = FileReader(path)) == NULL)
return; /* have been deleted, ignore this file */
if (file->flags & PGFILE_ZLIB)
in = ZlibReader(in);
if (file->flags & PGFILE_DATA)
in = BackupReader(in);
/* Writer */
join_path_components(path, to, file->name);
out = FileWriter(path);
do_copy(file, in, out);
/* update file permission */
if (chmod(path, file->mode) != 0)
ereport(ERROR,
(errcode_errno(),
errmsg("could not change mode of \"%s\": ", path)));
elog(LOG, "restore file: %s (%.2f%% of %lu bytes)",
file->name, 100.0 * file->written_size / file->size,
(unsigned long) file->size);
/* TODO: restore other attributes, including mtime. */
}
static void
do_copy(pgFile *file, Reader *in, Writer *out)
{
void *buffer;
size_t buflen;
size_t len;
pg_crc32 crc;
Assert(block_size > 0);
Assert(wal_block_size > 0);
buflen = Max(block_size, wal_block_size);
buffer = pgut_malloc(buflen); /* use malloc for memroy alignment */
/* copy contents */
while ((len = in->read(in, buffer, buflen)) > 0)
{
CHECK_FOR_INTERRUPTS();
out->write(out, buffer, len);
}
/* close in and out */
file->read_size = in->close(in);
file->written_size = out->close(out, &crc);
file->crc = crc;
file->flags |= PGFILE_CRC;
free(buffer);
}
/* ----------------------------------------------------------------
* File Reader
* ----------------------------------------------------------------
*/
typedef struct FReader
{
Reader base;
FILE *fp;
size_t done;
} FReader;
static size_t FReader_close(FReader *self);
static size_t FReader_read(FReader *self, void *buf, size_t len);
/* returns NULL if file not found */
static Reader *
FileReader(const char *path)
{
FReader *self;
FILE *fp;
if ((fp = pgut_fopen(path, "R")) == NULL)
return NULL;
self = pgut_new(FReader);
self->base.close = (ReaderClose) FReader_close;
self->base.read = (ReaderRead) FReader_read;
self->fp = fp;
self->done = 0;
return (Reader *) self;
}
static size_t
FReader_close(FReader *self)
{
size_t done = self->done;
fclose(self->fp);
free(self);
return done;
}
static size_t
FReader_read(FReader *self, void *buf, size_t len)
{
errno = 0;
len = fread(buf, 1, len, self->fp);
if (errno != 0)
ereport(ERROR,
(errcode_errno(),
errmsg("could not read file: ")));
self->done += len;
return len;
}
/* ----------------------------------------------------------------
* File Writer
* ----------------------------------------------------------------
*/
typedef struct FWriter
{
Writer base;
FILE *fp;
size_t done;
pg_crc32 crc;
} FWriter;
static size_t FWriter_close(FWriter *self, pg_crc32 *crc);
static void FWriter_write(FWriter *self, const void *buf, size_t len);
static Writer *
FileWriter(const char *path)
{
FWriter *self;
FILE *fp;
fp = pgut_fopen(path, "w");
self = pgut_new(FWriter);
self->base.close = (WriterClose) FWriter_close;
self->base.write = (WriterWrite) FWriter_write;
self->fp = fp;
self->done = 0;
INIT_CRC32(self->crc);
return (Writer *) self;
}
static size_t
FWriter_close(FWriter *self, pg_crc32 *crc)
{
size_t done = self->done;
if (crc)
{
FIN_CRC32(self->crc);
*crc = self->crc;
}
fclose(self->fp);
free(self);
return done;
}
static void
FWriter_write(FWriter *self, const void *buf, size_t len)
{
if (fwrite(buf, 1, len, self->fp) != len)
ereport(ERROR,
(errcode_errno(),
errmsg("could not write file: ")));
self->done += len;
COMP_CRC32(self->crc, buf, len);
}
#ifdef HAVE_LIBZ
#define Z_BUFSIZE (64 * 1024) /* 64KB */
/* ----------------------------------------------------------------
* LibZ Reader
* ----------------------------------------------------------------
*/
typedef struct ZReader
{
Reader base;
Reader *inner;
z_stream z;
Byte buf[Z_BUFSIZE];
} ZReader;
static size_t ZReader_close(ZReader *self);
static size_t ZReader_read(ZReader *self, void *buf, size_t len);
static Reader *
ZlibReader(Reader *inner)
{
ZReader *self = pgut_new(ZReader);
memset(self, 0, sizeof(ZReader));
self->base.close = (ReaderClose) ZReader_close;
self->base.read = (ReaderRead) ZReader_read;
self->inner = inner;
if (inflateInit(&self->z) != Z_OK)
elog(ERROR, "could not create z_stream: %s", self->z.msg);
return (Reader *) self;
}
static size_t
ZReader_close(ZReader *self)
{
size_t done;
if (inflateEnd(&self->z) != Z_OK)
elog(ERROR, "could not close z_stream: %s", self->z.msg);
done = self->inner->close(self->inner);
free(self);
return done;
}
static size_t
ZReader_read(ZReader *self, void *buf, size_t len)
{
self->z.next_out = buf;
self->z.avail_out = len;
while (self->z.avail_out > 0)
{
/* fill input buffer if empty */
if (self->z.avail_in == 0)
{
size_t sz = inner_read(self, self->buf, Z_BUFSIZE);
if (sz == 0)
break;
self->z.next_in = self->buf;
self->z.avail_in = sz;
}
/* inflate into output buffer */
switch (inflate(&self->z, Z_NO_FLUSH))
{
case Z_STREAM_END:
case Z_OK:
continue; /* ok, go next */
default:
elog(ERROR, "could not inflate z_stream: %s", self->z.msg);
}
}
return len - self->z.avail_out;
}
/* ----------------------------------------------------------------
* LibZ Writer
* ----------------------------------------------------------------
*/
typedef struct ZWriter
{
Writer base;
Writer *inner;
z_stream z;
Byte buf[Z_BUFSIZE];
} ZWriter;
static size_t ZWriter_close(ZWriter *self, pg_crc32 *crc);
static void ZWriter_write(ZWriter *self, const void *buf, size_t len);
static Writer *
ZlibWriter(Writer *inner)
{
ZWriter *self = pgut_new(ZWriter);
memset(self, 0, sizeof(ZWriter));
self->base.close = (WriterClose) ZWriter_close;
self->base.write = (WriterWrite) ZWriter_write;
self->inner = inner;
if (deflateInit(&self->z, Z_DEFAULT_COMPRESSION) != Z_OK)
elog(ERROR, "could not create z_stream: %s", self->z.msg);
self->z.next_out = self->buf;
self->z.avail_out = Z_BUFSIZE;
return (Writer *) self;
}
static size_t
ZWriter_close(ZWriter *self, pg_crc32 *crc)
{
size_t done;
/* finish zstream */
self->z.next_in = NULL;
self->z.avail_in = 0;
if (deflate(&self->z, Z_FINISH) < 0)
elog(ERROR, "could not finish z_stream: %s", self->z.msg);
inner_write(self, self->buf, Z_BUFSIZE - self->z.avail_out);
if (deflateEnd(&self->z) != Z_OK)
elog(ERROR, "could not close z_stream: %s", self->z.msg);
done = self->inner->close(self->inner, crc);
free(self);
return done;
}
static void
ZWriter_write(ZWriter *self, const void *buf, size_t len)
{
self->z.next_in = (void *) buf;
self->z.avail_in = len;
/* compresses until an input buffer becomes empty. */
while (self->z.avail_in > 0)
{
if (deflate(&self->z, Z_NO_FLUSH) < 0)
elog(ERROR, "could not deflate z_stream: %s", self->z.msg);
if (self->z.avail_out == 0)
{
inner_write(self, self->buf, Z_BUFSIZE);
self->z.next_out = self->buf;
self->z.avail_out = Z_BUFSIZE;
}
}
}
#else /* HAVE_LIBZ */
static Reader *
ZlibReader(Reader *inner)
{
elog(ERROR, "zlib is unavailable");
}
static Writer *
ZlibWriter(Writer *inner)
{
elog(ERROR, "zlib is unavailable");
}
#endif /* HAVE_LIBZ */
/* ----------------------------------------------------------------
* Reader for backup and restore data files
* ----------------------------------------------------------------
*/
typedef struct DReader
{
Reader base;
Reader *inner;
} DReader;
static size_t DReader_close(DReader *self);
static size_t Data_read(DReader *self, char *buf, size_t len);
static size_t Backup_read(DReader *self, DataPage *buf, size_t len);
static bool parse_header(const DataPage *page, uint16 *lower, uint16 *upper);
/*
* DataReader - Data file compresser.
*
* Unused free space is removed. If lsn is not NULL, only modified pages
* after the lsn will be copied.
*/
static Reader *
DataReader(Reader *inner)
{
DReader *self = pgut_new(DReader);
self->base.close = (ReaderClose) DReader_close;
self->base.read = (ReaderRead) Data_read;
self->inner = inner;
return (Reader *) self;
}
/*
* BackupReader - Data file decompresser.
*/
static Reader *
BackupReader(Reader *inner)
{
DReader *self = pgut_new(DReader);
self->base.close = (ReaderClose) DReader_close;
self->base.read = (ReaderRead) Backup_read;
self->inner = inner;
return (Reader *) self;
}
static size_t
DReader_close(DReader *self)
{
size_t done;
done = self->inner->close(self->inner);
free(self);
return done;
}
static size_t
Data_read(DReader *self, char *buf, size_t len)
{
DataPage page;
size_t done;
uint16 pd_lower;
uint16 pd_upper;
int upper_length;
/* read a page at once */
done = 0;
while (done < block_size)
{
size_t sz = inner_read(self, page.data + done, block_size - done);
if (sz == 0)
break;
done += sz;
}
if (done == 0)
return 0; /* eof */
if (done != block_size || !parse_header(&page, &pd_lower, &pd_upper))
goto bad_file;
/* XXX: repair fragmentation with PageRepairFragmentation? */
#if 0
/* if the page has not been modified since last backup, skip it */
if (lsn && !XLogRecPtrIsInvalid(page.pd_lsn) && XLByteLT(page.pd_lsn, *lsn))
goto retry;
#endif
upper_length = block_size - pd_upper;
if (len > pd_lower + upper_length)
elog(ERROR, "buffer too small");
/* remove hole of the page */
memcpy(buf, page.data, pd_lower);
memcpy(buf + pd_lower, page.data + pd_upper, upper_length);
return pd_lower + upper_length;
bad_file:
/* TODO: If a invalid data page was found, fallback to simple copy. */
elog(ERROR, "not a data file");
return 0;
}
static size_t
Backup_read(DReader *self, DataPage *buf, size_t len)
{
size_t sz;
uint16 pd_lower;
uint16 pd_upper;
int lower_remain;
int upper_length;
int header_size;
Assert(len >= block_size);
if (server_version < 80300)
header_size = SizeOfPageHeaderData_v80;
else
header_size = SizeOfPageHeaderData_v83;
/* read each page and write the page excluding hole */
if ((sz = inner_read(self, buf, header_size)) == 0)
return 0;
if (sz != header_size ||
!parse_header(buf, &pd_lower, &pd_upper))
goto bad_file;
lower_remain = pd_lower - header_size;
upper_length = block_size - pd_upper;
/* read remain lower and upper, and fill the hole with zero. */
if (inner_read(self, buf + header_size, lower_remain) != lower_remain ||
inner_read(self, buf + pd_upper, upper_length) != upper_length)
goto bad_file;
memset(buf + pd_lower, 0, pd_upper - pd_lower);
return block_size;
bad_file:
elog(ERROR, "not a data file");
return 0;
}
static bool
parse_header(const DataPage *page, uint16 *lower, uint16 *upper)
{
uint16 page_layout_version;
/* Determine page layout version */
if (server_version < 80300)
page_layout_version = PG_PAGE_LAYOUT_VERSION_v81;
else
page_layout_version = PG_PAGE_LAYOUT_VERSION_v83;
/* Check normal case */
if (server_version < 80300)
{
const PageHeaderData_v80 *v80 = &page->v80;
if (PageGetPageSize_v80(v80) == block_size &&
PageGetPageLayoutVersion_v80(v80) == page_layout_version &&
v80->pd_lower >= SizeOfPageHeaderData_v80 &&
v80->pd_lower <= v80->pd_upper &&
v80->pd_upper <= v80->pd_special &&
v80->pd_special <= block_size &&
v80->pd_special == MAXALIGN(v80->pd_special) &&
!XLogRecPtrIsInvalid(v80->pd_lsn))
{
*lower = v80->pd_lower;
*upper = v80->pd_upper;
return true;
}
}
else
{
const PageHeaderData_v83 *v83 = &page->v83;
if (PageGetPageSize_v83(v83) == block_size &&
PageGetPageLayoutVersion_v83(v83) == page_layout_version &&
(v83->pd_flags & ~PD_VALID_FLAG_BITS_v83) == 0 &&
v83->pd_lower >= SizeOfPageHeaderData_v83 &&
v83->pd_lower <= v83->pd_upper &&
v83->pd_upper <= v83->pd_special &&
v83->pd_special <= block_size &&
v83->pd_special == MAXALIGN(v83->pd_special) &&
!XLogRecPtrIsInvalid(v83->pd_lsn))
{
*lower = v83->pd_lower;
*upper = v83->pd_upper;
return true;
}
}
*lower = *upper = 0;
return false;
}

312
init.c
View File

@@ -1,156 +1,156 @@
/*-------------------------------------------------------------------------
*
* init.c: manage backup catalog.
*
* Copyright (c) 2009-2010, 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 */
join_path_components(path, 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 */
join_path_components(path, backup_path, TIMELINE_HISTORY_DIR);
dir_create_dir(path, DIR_PERMISSION);
/* read postgresql.conf */
if (pgdata)
{
join_path_components(path, pgdata, "postgresql.conf");
parse_postgresql_conf(path, &log_directory, &archive_command);
}
/* create pg_rman.ini */
join_path_components(path, 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);
join_path_components(srvlog_path, pgdata, log_directory);
}
}
else if (pgdata)
{
/* default: log_directory = 'pg_log' */
srvlog_path = pgut_malloc(MAXPGPATH);
join_path_components(srvlog_path, 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 */
}
/*-------------------------------------------------------------------------
*
* init.c: manage backup catalog.
*
* Copyright (c) 2009-2010, 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 */
join_path_components(path, 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 */
join_path_components(path, backup_path, TIMELINE_HISTORY_DIR);
dir_create_dir(path, DIR_PERMISSION);
/* read postgresql.conf */
if (pgdata)
{
join_path_components(path, pgdata, "postgresql.conf");
parse_postgresql_conf(path, &log_directory, &archive_command);
}
/* create pg_rman.ini */
join_path_components(path, 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);
join_path_components(srvlog_path, pgdata, log_directory);
}
}
else if (pgdata)
{
/* default: log_directory = 'pg_log' */
srvlog_path = pgut_malloc(MAXPGPATH);
join_path_components(srvlog_path, 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 */
}

379
parray.c
View File

@@ -1,182 +1,197 @@
/*-------------------------------------------------------------------------
*
* parray.c: pointer array collection.
*
* Copyright (c) 2009-2010, 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_new(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;
}
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);
}
/*-------------------------------------------------------------------------
*
* parray.c: pointer array collection.
*
* Copyright (c) 2009-2010, 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_new(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;
}
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, 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;
}
bool
parray_rm(parray *array, const void *key, int(*compare)(const void *, const void *))
{
int i;
for (i = 0; i < array->used; i++)
{
if (compare(&key, &array->data[i]) == 0)
{
parray_remove(array, i);
return true;
}
}
return false;
}
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);
}

View File

@@ -1,34 +1,35 @@
/*-------------------------------------------------------------------------
*
* parray.h: pointer array collection.
*
* Copyright (c) 2009-2010, 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 */
/*-------------------------------------------------------------------------
*
* parray.h: pointer array collection.
*
* Copyright (c) 2009-2010, 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 bool parray_rm(parray *array, const void *key, int(*compare)(const void *, const void *));
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 */

93
pg_crc.c Normal file
View File

@@ -0,0 +1,93 @@
/*-------------------------------------------------------------------------
*
* 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.
*
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*-------------------------------------------------------------------------
*/
/* 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
};

242
pg_ctl.c Normal file
View File

@@ -0,0 +1,242 @@
/*-------------------------------------------------------------------------
*
* pg_ctl.c: operations for control file
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#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 bool read_control_file(const char *path, ControlFileData *ctrl);
static pgpid_t
get_pgpid(void)
{
FILE *fp;
pgpid_t pid;
char path[MAXPGPATH];
snprintf(path, lengthof(path), "%s/postmaster.pid", pgdata);
if ((fp = pgut_fopen(path, "r")) == NULL)
return 0; /* No pid file, not an error on startup */
if (fscanf(fp, "%ld", &pid) != 1)
elog(ERROR_INCOMPATIBLE, "invalid data in PID file \"%s\"", path);
fclose(fp);
return 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);
}
static void
compare_uint32(const char *name, uint32 server, uint32 backup)
{
if (server != backup)
elog(ERROR_INCOMPATIBLE,
"incompatible %s: server=%u / backup=%u",
name, server, backup);
}
static void
compare_uint64(const char *name, uint64 server, uint64 backup)
{
if (server != backup)
elog(ERROR_INCOMPATIBLE,
"incompatible %s: server=" UINT64_FORMAT " / backup=" UINT64_FORMAT,
name, server, backup);
}
static void
compare_double(const char *name, double server, double backup)
{
if (server != backup)
elog(ERROR_INCOMPATIBLE,
"incompatible %s: server=%f / backup=%f",
name, server, backup);
}
static void
compare_bool(const char *name, bool server, bool backup)
{
if ((server && !backup) || (!server && backup))
elog(ERROR_INCOMPATIBLE,
"incompatible %s: server=%s / backup=%s",
name, (server ? "true" : "false"), (backup ? "true" : "false"));
}
/* verify control file */
bool
verify_control_file(const char *pgdata, const char *catalog)
{
char path[MAXPGPATH];
ControlFileData ctrl;
bool in_pgdata;
bool in_backup;
snprintf(path, MAXPGPATH, "%s/global/pg_control", pgdata);
in_pgdata = read_control_file(path, &pgControlFile);
snprintf(path, MAXPGPATH, "%s/pg_control", catalog);
in_backup = read_control_file(path, &ctrl);
if (in_pgdata)
{
if (in_backup)
{
/* compare control files */
compare_uint32("pg_control version number",
pgControlFile.pg_control_version, ctrl.pg_control_version);
compare_uint32("catalog version number",
pgControlFile.catalog_version_no, ctrl.catalog_version_no);
compare_uint64("database system identifier",
pgControlFile.system_identifier, ctrl.system_identifier);
compare_uint32("maximum data alignment",
pgControlFile.maxAlign, ctrl.maxAlign);
compare_uint32("float format",
pgControlFile.floatFormat, ctrl.floatFormat);
compare_double("database block size",
pgControlFile.blcksz, ctrl.blcksz);
compare_uint32("blocks per segment of large relation",
pgControlFile.relseg_size, ctrl.relseg_size);
compare_uint32("wal block size",
pgControlFile.xlog_blcksz, ctrl.xlog_blcksz);
compare_uint32("bytes per wal segment",
pgControlFile.xlog_seg_size, ctrl.xlog_seg_size);
compare_uint32("maximum length of identifiers",
pgControlFile.nameDataLen, ctrl.nameDataLen);
compare_uint32("maximum columns in an index",
pgControlFile.indexMaxKeys, ctrl.indexMaxKeys);
compare_uint32("maximum size of a toast chunk",
pgControlFile.toast_max_chunk_size, ctrl.toast_max_chunk_size);
compare_bool("date/time type storage",
pgControlFile.enableIntTimes, ctrl.enableIntTimes);
compare_bool("float4 argument passing",
pgControlFile.float4ByVal, ctrl.float4ByVal);
compare_bool("float8 argument passing",
pgControlFile.float8ByVal, ctrl.float8ByVal);
}
else
{
/* write in_backup pg_control */
FILE *fp = pgut_fopen(path, "w");
if (fwrite(&pgControlFile, 1, sizeof(ControlFileData), fp) != sizeof(ControlFileData))
{
fclose(fp);
unlink(path);
ereport(ERROR,
(errcode_errno(),
errmsg("could not write control file \"%s\": ", path)));
}
fclose(fp);
}
return true;
}
else
{
if (in_backup)
{
/* volatile parts are unavialable */
memset(((char *) &ctrl) + offsetof(ControlFileData, state), 0,
offsetof(ControlFileData, maxAlign) - offsetof(ControlFileData, state));
pgControlFile = ctrl;
}
else
ereport(ERROR,
(errcode(ENOENT),
errmsg("control files not found")));
return false;
}
}
/*
* FIXME: ControlFileData might be changed in versions.
*/
static bool
read_control_file(const char *path, ControlFileData *ctrl)
{
FILE *fp;
pg_crc32 crc;
if ((fp = pgut_fopen(path, "r")) == NULL)
{
memset(ctrl, 0, sizeof(ControlFileData));
return false;
}
if (fread(ctrl, 1, sizeof(ControlFileData), fp) != sizeof(ControlFileData))
elog(ERROR_INCOMPATIBLE,
"could not read control file \"%s\": %s", path, strerror(errno));
fclose(fp);
/* Check the CRC. */
INIT_CRC32(crc);
COMP_CRC32(crc, ctrl, offsetof(ControlFileData, crc));
FIN_CRC32(crc);
if (!EQ_CRC32(crc, ctrl->crc))
elog(ERROR_INCOMPATIBLE,
"bad CRC checksum for control file \"%s\"", path);
return true;
}

606
pg_rman.c
View File

@@ -1,303 +1,303 @@
/*-------------------------------------------------------------------------
*
* pg_rman.c: Backup/Recovery manager for PostgreSQL.
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
const char *PROGRAM_VERSION = "1.1.2";
const char *PROGRAM_URL = "http://code.google.com/p/pg-rman/";
const char *PROGRAM_EMAIL = "http://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" , &current.with_serverlog , SOURCE_ENV },
{ 'b', 'Z', "compress-data" , &current.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(&current);
/* 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];
join_path_components(path, 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);
memset(&tm, 0, sizeof(tm));
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;
tm.tm_isdst = -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);
}
/*-------------------------------------------------------------------------
*
* pg_rman.c: Backup/Recovery manager for PostgreSQL.
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
const char *PROGRAM_VERSION = "1.2.0";
const char *PROGRAM_URL = "http://code.google.com/p/pg-rman/";
const char *PROGRAM_EMAIL = "http://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" , &current.with_serverlog , SOURCE_ENV },
{ 'b', 'Z', "compress-data" , &current.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(&current);
/* 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];
join_path_components(path, 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);
memset(&tm, 0, sizeof(tm));
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;
tm.tm_isdst = -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);
}

593
pg_rman.h
View File

@@ -1,288 +1,305 @@
/*-------------------------------------------------------------------------
*
* pg_rman.h: Backup/Recovery manager for PostgreSQL.
*
* Copyright (c) 2009-2010, 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"
#if PG_VERSION_NUM < 80200
#define XLOG_BLCKSZ BLCKSZ
#endif
/* 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, /* archive 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);
extern int get_server_version(void);
/* 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 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, int server_version);
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 bool 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 bool 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 */
/*-------------------------------------------------------------------------
*
* pg_rman.h: Backup/Recovery manager for PostgreSQL.
*
* Copyright (c) 2009-2010, 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"
#if PG_VERSION_NUM < 80200
#define XLOG_BLCKSZ BLCKSZ
#endif
/* 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 PG_TBLSPC_DIR "pg_tblspc"
#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"
#define SNAPSHOT_SCRIPT_FILE "snapshot_script"
/* Snapshot script command */
#define SNAPSHOT_FREEZE "freeze"
#define SNAPSHOT_UNFREEZE "unfreeze"
#define SNAPSHOT_SPLIT "split"
#define SNAPSHOT_RESYNC "resync"
#define SNAPSHOT_MOUNT "mount"
#define SNAPSHOT_UMOUNT "umount"
/* 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, /* archive 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;
/*
* return pointer that exceeds the length of prefix from character string.
* ex. str="/xxx/yyy/zzz", prefix="/xxx/yyy", return="zzz".
*/
#define JoinPathEnd(str, prefix) \
((strlen(str) <= strlen(prefix)) ? "" : str + strlen(prefix) + 1)
/* 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);
extern int get_server_version(void);
/* 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, const char *prefix);
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 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, int server_version);
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 bool 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 bool 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 */

View File

@@ -1,193 +1,193 @@
/*-------------------------------------------------------------------------
*
* pgut-port.c
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "c.h"
#include "pgut-port.h"
#undef flock
#include <unistd.h>
#include <fcntl.h>
#ifdef WIN32
#include <winioctl.h>
#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;
}
#endif
#ifdef PGUT_FLOCK
#ifdef WIN32
int
pgut_flock(int fd, int operation)
{
BOOL ret;
HANDLE handle = (HANDLE) _get_osfhandle(fd);
DWORD lo = 0;
DWORD hi = 0;
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;
}
#else
int
pgut_flock(int fd, int operation)
{
struct flock lck;
int cmd;
memset(&lck, 0, sizeof(lck));
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
lck.l_pid = getpid();
if (operation & LOCK_UN)
lck.l_type = F_UNLCK;
else if (operation & LOCK_EX)
lck.l_type = F_WRLCK;
else
lck.l_type = F_RDLCK;
if (operation & LOCK_NB)
cmd = F_SETLK;
else
cmd = F_SETLKW;
return fcntl(fd, cmd, &lck);
}
#endif
#endif
/*-------------------------------------------------------------------------
*
* pgut-port.c
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "c.h"
#include "pgut-port.h"
#undef flock
#include <unistd.h>
#include <fcntl.h>
#ifdef WIN32
#include <winioctl.h>
#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;
}
#endif
#ifdef PGUT_FLOCK
#ifdef WIN32
int
pgut_flock(int fd, int operation)
{
BOOL ret;
HANDLE handle = (HANDLE) _get_osfhandle(fd);
DWORD lo = 0;
DWORD hi = 0;
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;
}
#else
int
pgut_flock(int fd, int operation)
{
struct flock lck;
int cmd;
memset(&lck, 0, sizeof(lck));
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
lck.l_pid = getpid();
if (operation & LOCK_UN)
lck.l_type = F_UNLCK;
else if (operation & LOCK_EX)
lck.l_type = F_WRLCK;
else
lck.l_type = F_RDLCK;
if (operation & LOCK_NB)
cmd = F_SETLK;
else
cmd = F_SETLKW;
return fcntl(fd, cmd, &lck);
}
#endif
#endif

View File

@@ -1,50 +1,50 @@
/*-------------------------------------------------------------------------
*
* pgut-port.h
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#ifndef PGUT_PORT_H
#define PGUT_PORT_H
/*
* readlink ports
*/
#ifdef WIN32
#define S_IFLNK (0)
#define S_IRWXG (0)
#define S_IRWXO (0)
#define S_ISLNK(mode) (0)
extern ssize_t readlink(const char *path, char *target, size_t size);
#endif
/*
* flock ports
*/
#ifndef LOCK_EX
#define PGUT_FLOCK
#undef LOCK_SH
#undef LOCK_EX
#undef LOCK_UN
#undef LOCK_NB
#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 pgut_flock(int fd, int operation);
#define flock pgut_flock
#endif
#endif /* PGUT_PORT_H */
/*-------------------------------------------------------------------------
*
* pgut-port.h
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#ifndef PGUT_PORT_H
#define PGUT_PORT_H
/*
* readlink ports
*/
#ifdef WIN32
#define S_IFLNK (0)
#define S_IRWXG (0)
#define S_IRWXO (0)
#define S_ISLNK(mode) (0)
extern ssize_t readlink(const char *path, char *target, size_t size);
#endif
/*
* flock ports
*/
#ifndef LOCK_EX
#define PGUT_FLOCK
#undef LOCK_SH
#undef LOCK_EX
#undef LOCK_UN
#undef LOCK_NB
#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 pgut_flock(int fd, int operation);
#define flock pgut_flock
#endif
#endif /* PGUT_PORT_H */

File diff suppressed because it is too large Load Diff

View File

@@ -1,253 +1,253 @@
/*-------------------------------------------------------------------------
*
* pgut.h
*
* Copyright (c) 2009-2010, 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 signed integer
* u: 32bit unsigned integer
* I: 64bit signed 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);
#define pgut_new(type) ((type *) pgut_malloc(sizeof(type)))
#define pgut_newarray(type, n) ((type *) pgut_malloc(sizeof(type) * (n)))
/*
* file operations
*/
extern FILE *pgut_fopen(const char *path, const char *mode, bool missing_ok);
/*
* 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 */
/*-------------------------------------------------------------------------
*
* pgut.h
*
* Copyright (c) 2009-2010, 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 signed integer
* u: 32bit unsigned integer
* I: 64bit signed 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);
#define pgut_new(type) ((type *) pgut_malloc(sizeof(type)))
#define pgut_newarray(type, n) ((type *) pgut_malloc(sizeof(type) * (n)))
/*
* file operations
*/
extern FILE *pgut_fopen(const char *path, const char *mode, bool missing_ok);
/*
* 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 */

170
queue.c Normal file
View File

@@ -0,0 +1,170 @@
/*-------------------------------------------------------------------------
*
* queue.c: Job queue with thread pooling.
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include "pgut/pgut-pthread.h"
struct JobQueue
{
pthread_mutex_t mutex; /* protects the queue data */
pthread_cond_t anyjobs; /* fired if any jobs */
pthread_cond_t nojobs; /* fired if no jobs */
List *threads; /* list of worker thread handles */
List *jobs; /* pending jobs */
volatile int maximum; /* maximum allowed threads */
volatile int idle; /* number of idle threads */
volatile bool terminated; /* in termination? */
};
static void *worker_thread(void *arg);
JobQueue *
JobQueue_new(int nthreads)
{
JobQueue *queue;
Assert(nthreads >= 1);
queue = pgut_new(JobQueue);
pthread_mutex_init(&queue->mutex, NULL);
pthread_cond_init(&queue->anyjobs, NULL);
pthread_cond_init(&queue->nojobs, NULL);
queue->threads = NIL;
queue->jobs = NIL;
queue->maximum = nthreads;
queue->idle = 0;
queue->terminated = false;
return queue;
}
/*
* Job must be allocated with malloc. The ownership will be granted to
* the queue.
*/
void
JobQueue_push(JobQueue *queue, Job *job)
{
Assert(queue);
Assert(!queue->terminated);
Assert(job);
Assert(job->routine);
pgut_mutex_lock(&queue->mutex);
queue->jobs = lappend(queue->jobs, job);
if (queue->idle > 0)
pthread_cond_signal(&queue->anyjobs);
else if (list_length(queue->threads) < queue->maximum)
{
pthread_t th;
if (pthread_create(&th, NULL, worker_thread, queue))
ereport(ERROR,
(errcode_errno(),
errmsg("could not create thread: ")));
queue->threads = lappend(queue->threads, (void *) th);
Assert(list_length(queue->threads) <= queue->maximum);
}
pthread_mutex_unlock(&queue->mutex);
}
/* wait for all job finished */
void
JobQueue_wait(JobQueue *queue)
{
Assert(queue);
Assert(!queue->terminated);
pgut_mutex_lock(&queue->mutex);
while (queue->jobs || queue->idle < list_length(queue->threads))
pgut_cond_wait(&queue->nojobs, &queue->mutex);
pthread_mutex_unlock(&queue->mutex);
}
/* Free job queue. All pending jobs are also discarded. */
void
JobQueue_free(JobQueue *queue)
{
ListCell *cell;
if (queue == NULL)
return;
Assert(!queue->terminated);
/* Terminate all threads. */
pgut_mutex_lock(&queue->mutex);
queue->terminated = true;
pthread_cond_broadcast(&queue->anyjobs);
pthread_mutex_unlock(&queue->mutex);
/*
* Wait for all threads.
* XXX: cancel thread for long running jobs?
*/
foreach(cell, queue->threads)
{
pthread_t th = (pthread_t) lfirst(cell);
pthread_join(th, NULL);
}
list_free(queue->threads);
/* Free all pending jobs, though it must be avoided. */
list_free_deep(queue->jobs);
pthread_cond_destroy(&queue->nojobs);
pthread_cond_destroy(&queue->anyjobs);
pthread_mutex_destroy(&queue->mutex);
free(queue);
}
static void *
worker_thread(void *arg)
{
JobQueue *queue = (JobQueue *) arg;
pgut_mutex_lock(&queue->mutex);
while (!queue->terminated)
{
Job *job;
if (queue->jobs == NIL)
{
queue->idle++;
/* notify if done all jobs */
if (queue->idle >= list_length(queue->threads))
pthread_cond_broadcast(&queue->nojobs);
pgut_cond_wait(&queue->anyjobs, &queue->mutex);
queue->idle--;
if (queue->terminated)
break;
}
if (queue->jobs == NIL)
continue; /* job might have done by another worker */
job = linitial(queue->jobs);
queue->jobs = list_delete_first(queue->jobs);
pthread_mutex_unlock(&queue->mutex);
job->routine(job);
free(job);
pgut_mutex_lock(&queue->mutex);
}
pthread_mutex_unlock(&queue->mutex);
return NULL;
}

1942
restore.c

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
#!/bin/sh
#############################################################################
# Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
#############################################################################
CMD_SUDO="/usr/bin/sudo"
CMD_LVCREATE="${CMD_SUDO} /usr/sbin/lvcreate"
CMD_LVREMOVE="${CMD_SUDO} /usr/sbin/lvremove"
CMD_MOUNT="${CMD_SUDO} /bin/mount"
CMD_UMOUNT="${CMD_SUDO} /bin/umount"
INFO=-2
WARNING=-1
ERROR=0
#
# Please set the information necessary for the snapshot acquisition
# to each array.
#
# The following arrays are one sets.
#
# SNAPSHOT_NAME .... name for snapshot volume
# SNAPSHOT_SIZE .... size to allocate for snapshot volume
# SNAPSHOT_VG ...... volume group to which logical volume to create snapshot belongs
# SNAPSHOT_LV ...... logical volume to create snapshot
# SNAPSHOT_MOUNT ... mount point to file-system of snapshot volume
#
# increase and decrease the slot in proportion to the number of acquisition
# of snapshots.
#
# example:
#
# SNAPSHOT_NAME[0]="snap00"
# SNAPSHOT_SIZE[0]="2G"
# SNAPSHOT_VG[0]="/dev/VolumeGroup00"
# SNAPSHOT_LV[0]="/dev/VolumeGroup00/LogicalVolume00"
# SNAPSHOT_MOUNT[0]="/mnt/pgdata"
#
# SNAPSHOT_NAME[1]="snap01"
# SNAPSHOT_SIZE[1]="2G"
# SNAPSHOT_VG[1]="/dev/VolumeGroup00"
# SNAPSHOT_LV[1]="/dev/VolumeGroup00/LogicalVolume01"
# SNAPSHOT_MOUNT[1]="/mnt/tblspc/account"
#
#SNAPSHOT_NAME[0]=""
#SNAPSHOT_SIZE[0]=""
#SNAPSHOT_VG[0]=""
#SNAPSHOT_LV[0]=""
#SNAPSHOT_MOUNT[0]=""
#
# Please set the tablespace name and tablespace storage path in snapshot
# to each array.
#
# The following arrays are one sets.
#
# SNAPSHOT_TBLSPC ........ name of tablespace in snapshot volume
# SNAPSHOT_TBLSPC_DIR .... stored directory of tablespace in snapshot volume
#
# Note: set 'PG-DATA' to SNAPSHOT_TBLSPC when PGDATA in snapshot volume.
#
# increase and decrease the slot in proportion to the number of acquisition
# of tablespace.
#
# example:
#
# SNAPSHOT_TBLSPC[0]="PG-DATA"
# SNAPSHOT_TBLSPC_DIR[0]="/mnt/pgdata/pgdata"
# SNAPSHOT_TBLSPC[1]="custom"
# SNAPSHOT_TBLSPC_DIR[1]="/mnt/tblspc_custom/tblspc/custom"
# SNAPSHOT_TBLSPC[2]="account"
# SNAPSHOT_TBLSPC_DIR[2]="/mnt/tblspc_account/tblspc/account"
#
#SNAPSHOT_TBLSPC[0]=""
#SNAPSHOT_TBLSPC_DIR[0]=""
#
# argument of the command.
# this variables are set by set_args().
#
ARGS_SS_NAME="" # SNAPSHOT_NAME[N]
ARGS_SS_SIZE="" # SNAPSHOT_SIZE[N]
ARGS_SS_VG="" # SNAPSHOT_VG[N]
ARGS_SS_LV="" # SNAPSHOT_LV[N]
ARGS_SS_MOUNT="" # SNAPSHOT_MOUNT[N]
#
# implement of interface 'freeze'.
# don't remove this function even if there is no necessity.
#
function freeze()
{
# nothing to do
return
}
#
# implement of interface 'unfreeze'.
# don't remove this function even if there is no necessity.
#
function unfreeze()
{
# nothing to do
return
}
#
# implement of interface 'split'.
# create a snapshot volume from the setting of the specified slot.
# don't remove this function even if there is no necessity.
#
function split()
{
local i=0
for ss_name in "${SNAPSHOT_NAME[@]}"
do
set_args "${ss_name}" "${SNAPSHOT_SIZE[${i}]}" "" "${SNAPSHOT_LV[${i}]}" ""
execute_split
i=$(expr ${i} + 1)
done
# print tablespace name
i=0
for tblspc in "${SNAPSHOT_TBLSPC[@]}"
do
local tblspc="${SNAPSHOT_TBLSPC[${i}]}"
echo "${tblspc}"
i=$(expr ${i} + 1)
done
return
}
#
# implement of interface 'resync'.
# remove a snapshot volume from the setting of the specified slot.
# don't remove this function even if there is no necessity.
#
function resync()
{
local i=0
for ss_name in "${SNAPSHOT_NAME[@]}"
do
set_args "${ss_name}" "" "${SNAPSHOT_VG[${i}]}" "" ""
execute_resync
i=$(expr ${i} + 1)
done
return
}
#
# implement of interface 'mount'.
# create mount point of the snapshot volume to the file-system.
# don't remove this function even if there is no necessity.
#
function mount()
{
local i=0
for ss_name in "${SNAPSHOT_NAME[@]}"
do
set_args "${ss_name}" "" "${SNAPSHOT_VG[${i}]}" "" "${SNAPSHOT_MOUNT[${i}]}"
execute_mount
i=$(expr ${i} + 1)
done
# print tablespace name and stored directory
i=0
for tblspc in "${SNAPSHOT_TBLSPC[@]}"
do
local tblspc_mp="${SNAPSHOT_TBLSPC_DIR[${i}]}"
echo "${tblspc}=${tblspc_mp}"
i=$(expr ${i} + 1)
done
return
}
#
# implement of interface 'umount'.
# remove mount point of the snapshot volume from the file-system.
# don't remove this function even if there is no necessity.
#
function umount()
{
for ss_mp in "${SNAPSHOT_MOUNT[@]}"
do
set_args "" "" "" "" "${ss_mp}"
execute_umount
done
return
}
#
# create the snapshot volume.
#
function execute_split()
{
${CMD_LVCREATE} --snapshot --size=${ARGS_SS_SIZE} --name="${ARGS_SS_NAME}" "${ARGS_SS_LV}" > /dev/null
[ ${?} -ne 0 ] && \
print_log ${ERROR} "${CMD_LVCREATE} command failed: ${ARGS_SS_LV}"
}
#
# remove the snapshot volume.
#
function execute_resync()
{
${CMD_LVREMOVE} -f "${ARGS_SS_VG}/${ARGS_SS_NAME}" > /dev/null
[ ${?} -ne 0 ] && \
print_log ${ERROR} "${CMD_LVREMOVE} command failed: ${ARGS_SS_VG}/${ARGS_SS_NAME}"
}
#
# mount the snapshot volume to file-system.
#
function execute_mount()
{
${CMD_MOUNT} "${ARGS_SS_VG}/${ARGS_SS_NAME}" "${ARGS_SS_MOUNT}" > /dev/null
[ ${?} -ne 0 ] && \
print_log ${ERROR} "${CMD_MOUNT} command failed: ${ARGS_SS_MOUNT}"
}
#
# unmount the directory from file-system in snapshot volume.
#
function execute_umount()
{
${CMD_UMOUNT} "${ARGS_SS_MOUNT}" > /dev/null
[ ${?} -ne 0 ] && \
print_log ${ERROR} "${CMD_UMOUNT} command failed: ${ARGS_SS_MOUNT}"
}
#
# set argument of command to execute.
#
set_args()
{
ARGS_SS_NAME="${1}"
ARGS_SS_SIZE="${2}"
ARGS_SS_VG="${3}"
ARGS_SS_LV="${4}"
ARGS_SS_MOUNT="${5}"
}
#
# output the log message and abort the script when level <= ERROR
#
function print_log()
{
local level="${1}"
local message="${2}"
# if cleanup enable change ERROR to WARNING
[ -n "${cleanup}" -a ${level} -ge 0 ] && \
level=${WARNING}
case "${level}" in
${INFO} ) # INFO
echo "INFO: ${message}" 1>&2
;;
${WARNING} ) # WARNING
echo "WARNING: ${message}" 1>&2
;;
${ERROR} ) # ERROR
echo "ERROR: ${message}" 1>&2
;;
esac
[ ${level} -ge 0 ] && exit
}
#
# main
#
command="${1}"
cleanup="${2}"
case "${command}" in
"freeze" )
freeze
;;
"unfreeze" )
unfreeze
;;
"split" )
split
;;
"resync" )
resync
;;
"mount" )
mount
;;
"umount" )
umount
;;
* )
print_log ${ERROR} "specified invalid command: ${command} (internal error)"
;;
esac
echo "SUCCESS"
exit

510
show.c
View File

@@ -1,255 +1,255 @@
/*-------------------------------------------------------------------------
*
* show.c: show backup catalog.
*
* Copyright (c) 2009-2010, 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);
}
/*-------------------------------------------------------------------------
*
* show.c: show backup catalog.
*
* Copyright (c) 2009-2010, 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);
}

164
util.c
View File

@@ -1,82 +1,82 @@
/*-------------------------------------------------------------------------
*
* util.c: log messages to log file or stderr, and misc code.
*
* Copyright (c) 2009-2010, 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);
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';
}
/*-------------------------------------------------------------------------
*
* util.c: log messages to log file or stderr, and misc code.
*
* Copyright (c) 2009-2010, 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);
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';
}

486
utils.c Normal file
View File

@@ -0,0 +1,486 @@
/*-------------------------------------------------------------------------
*
* utils.c:
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include <dirent.h>
#include <time.h>
#include <limits.h>
#ifdef WIN32
#include <winioctl.h>
#endif
/*
* Convert time_t value to ISO-8601 format string.
* The size of buffer must be larger than DATESTRLEN.
*/
char *
date2str(char *buf, time_t date)
{
struct tm *tm = localtime(&date);
strftime(buf, DATESTRLEN, "%Y-%m-%d %H:%M:%S", tm);
return buf;
}
/*
* The size of buffer must be larger than TIMESTRLEN.
*/
char *
time2str(char *buf, time_t time)
{
/* set empty if nagative duration */
if (time < 0)
buf[0] = '\0';
else if (time >= 100 * 24 * 60 * 60)
snprintf(buf, TIMESTRLEN, "%.1fd", time / 86400.0);
else if (time >= 60 * 60)
snprintf(buf, TIMESTRLEN, "%.1fh", time / 3600.0);
else if (time >= 60)
snprintf(buf, TIMESTRLEN, "%.1fm", time / 60.0);
else
snprintf(buf, TIMESTRLEN, "%lds", (long) time);
return buf;
}
/*
* The size of buffer must be larger than SIZESTRLEN.
*/
char *
size2str(char *buf, int64 size)
{
int exp;
int64 base;
double n;
static const char *units[] = { "B ", "KB", "MB", "GB", "TB", "PB" };
/* set empty if nagative size */
if (size < 0)
{
buf[0] = '\0';
return buf;
}
/* determine the unit */
for (exp = 0, base = 1;
exp < lengthof(units) && base * 1024 < size;
++exp, base *= 1024)
;
n = size / (double) base;
if (n >= 100.0)
snprintf(buf, SIZESTRLEN, "%4.0f%s", n, units[exp]);
else if (n >= 10.0)
snprintf(buf, SIZESTRLEN, "%3.1f%s", n, units[exp]);
else
snprintf(buf, SIZESTRLEN, "%3.2f%s", n, units[exp]);
return buf;
}
/*
* Parse for backup mode. empty input is treated as full.
*/
BackupMode
parse_backup_mode(const char *value)
{
const char *v = value;
size_t len;
if (v == NULL)
return MODE_FULL; /* null input is full. */
while (IsSpace(*v)) { v++; }
if ((len = strlen(v)) == 0)
return MODE_FULL; /* empty input is full. */
/* Do a prefix match. For example, "incr" means incremental. */
if (pg_strncasecmp("full", v, len) == 0)
return MODE_FULL;
else if (pg_strncasecmp("incremental", v, len) == 0)
return MODE_INCREMENTAL;
else if (pg_strncasecmp("archive", v, len) == 0)
return MODE_ARCHIVE;
ereport(ERROR,
(errcode(EINVAL),
errmsg("invalid backup mode: '%s'", value)));
return (BackupMode) -1;
}
XLogName
parse_xlogname(const char *value)
{
XLogName xlog;
char junk[2];
if (sscanf(value, "%08X%08X%08X%1s",
&xlog.tli, &xlog.log, &xlog.seg, junk) != 3)
ereport(ERROR,
(errcode(EINVAL),
errmsg("invalid xlog name: '%s'", value)));
return xlog;
}
/* return max value of time_t */
time_t
time_max(void)
{
static time_t value = 0;
if (value == 0)
{
if (sizeof(time_t) > sizeof(int32))
{
struct tm tm = { 0 };
/* '9999-12-31 23:59:59' for 64bit time_t */
tm.tm_year = 9999 - 1900;
tm.tm_mon = 12 - 1;
tm.tm_mday = 31;
tm.tm_hour = 23;
tm.tm_min = 59;
tm.tm_sec = 59;
value = mktime(&tm);
}
else
{
/* '2038-01-19 03:14:07' for 32bit time_t */
value = INT_MAX;
}
}
return value;
}
/*
* Create range object from one or two arguments.
* All not-digit characters in the argument(s) are igonred.
*/
pgRange
make_range(int argc, char * const *argv)
{
pgRange range;
const char *arg1;
const char *arg2;
size_t len;
char *tmp;
int i;
struct tm tm;
char junk[2];
/* takes 0, 1, or 2 arguments */
if (argc > 2)
ereport(ERROR,
(errcode(EINVAL),
errmsg("too many arguments")));
/* no input means unlimited range */
if (argc < 1)
{
range.begin = 0;
range.end = time_max();
return range;
}
arg1 = argv[0];
arg2 = (argc > 1 ? argv[1] : "");
/* tmp = replace( concat(arg1, arg2), !isalnum, ' ' ) */
tmp = pgut_malloc(strlen(arg1) + strlen(arg2) + 1);
len = 0;
for (i = 0; arg1[i]; i++)
tmp[len++] = (IsAlnum(arg1[i]) ? arg1[i] : ' ');
for (i = 0; arg2[i]; i++)
tmp[len++] = (IsAlnum(arg2[i]) ? arg2[i] : ' ');
tmp[len] = '\0';
/* parse for "YYYY-MM-DD HH:MI:SS" */
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;
i = sscanf(tmp, "%04d %02d %02d %02d %02d %02d%1s",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec, junk);
if (i < 1 || 6 < i)
ereport(ERROR,
(errcode(EINVAL),
errmsg("invalid range syntax: '%s'", tmp)));
free(tmp);
/* adjust year */
if (tm.tm_year < 100)
tm.tm_year += 2000 - 1900;
else if (tm.tm_year >= 1900)
tm.tm_year -= 1900;
/* adjust month */
if (i > 1)
tm.tm_mon -= 1;
range.begin = mktime(&tm);
switch (i)
{
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);
return range;
}
/*
* check path is a directory and returns errno of opendir.
* ENOENT is treated as succeeded if missing_ok.
*/
int
check_dir(const char *path, bool missing_ok)
{
DIR *dir;
if ((dir = opendir(path)) == NULL)
{
if (missing_ok && errno == ENOENT)
return 0;
else
return errno;
}
closedir(dir);
return 0;
}
/*
* make sure the directory either doesn't exist or is empty
*
* Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty,
* or -1 if trouble accessing directory
*/
void
make_empty_dir(const char *path)
{
DIR *dir;
errno = 0;
if ((dir = opendir(path)) == NULL)
{
/* Directory does not exist. */
if (errno != ENOENT)
ereport(ERROR,
(errcode_errno(),
errmsg("could not access directory \"%s\": ", path)));
pgut_mkdir(path);
}
else
{
/* Directory exists. */
struct dirent *file;
while ((file = readdir(dir)) != NULL)
{
if (strcmp(".", file->d_name) == 0 ||
strcmp("..", file->d_name) == 0)
{
/* skip this and parent directory */
continue;
}
else
{
/* Present and not empty */
closedir(dir);
ereport(ERROR,
(errcode(EEXIST),
errmsg("directory \"%s\" exists but is not empty", path)));
}
}
#ifdef WIN32
/*
* This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4),
* but not in released version
*/
if (GetLastError() == ERROR_NO_MORE_FILES)
errno = 0;
#endif
closedir(dir);
}
}
/*
* Remove files recursively, but follow symbolic link to directories.
* We remove the symbolic link files, but delete the linked directories.
*/
void
remove_file(const char *path)
{
remove_children(path);
if (remove(path) != 0 && errno != ENOENT)
elog(ERROR, "could not remove file \"%s\": %s", path, strerror(errno));
}
void
remove_children(const char *path)
{
DIR *dir;
/* try to open as directory and remove children. */
if ((dir = opendir(path)) != NULL)
{
struct dirent *dent;
while ((dent = readdir(dir)) != NULL)
{
char child[MAXPGPATH];
/* skip entries point current dir or parent dir */
if (strcmp(dent->d_name, ".") == 0 ||
strcmp(dent->d_name, "..") == 0)
continue;
join_path_components(child, path, dent->d_name);
remove_file(child);
}
closedir(dir);
}
}
#ifdef WIN32
#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;
}
#endif

View File

@@ -1,185 +1,185 @@
/*-------------------------------------------------------------------------
*
* validate.c: validate backup files.
*
* Copyright (c) 2009-2010, 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;
}
/*-------------------------------------------------------------------------
*
* validate.c: validate backup files.
*
* Copyright (c) 2009-2010, 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;
}

184
verify.c Normal file
View File

@@ -0,0 +1,184 @@
/*-------------------------------------------------------------------------
*
* verify.c: verify backup files.
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
*-------------------------------------------------------------------------
*/
#include "pg_rman.h"
#include <unistd.h>
static bool verify_files(List *files, const char *root);
static bool verify_file(pgFile *file, const char *root);
typedef struct VerifyJob
{
void (*routine)(struct VerifyJob *);
pgFile *file;
const char *root;
volatile bool *ok;
} VerifyJob;
/* copy the file into backup */
static void
verify_routine(VerifyJob *job)
{
if (*job->ok && !verify_file(job->file, job->root))
*job->ok = false;
}
#define VERIFY_MASK (BACKUP_MASK(BACKUP_DONE))
void
do_verify(pgRange range)
{
Database db;
List *backups;
ListCell *cell;
db = db_open();
backups = db_list_backups(db, range, VERIFY_MASK);
foreach (cell, backups)
verify_backup(db, lfirst(cell));
db_close(db);
list_free_deep(backups);
}
/*
* Verify files in the backup and update the status to OK or BAD.
*/
void
verify_backup(Database db, pgBackup *backup)
{
char root[MAXPGPATH];
char datetime[DATESTRLEN];
List *dbfiles;
List *arclogs = NIL;
bool ok;
if (backup->status == BACKUP_OK)
return; /* already verified */
elog(INFO, "verify: %s", date2str(datetime, backup->start_time));
make_backup_path(root, backup->start_time);
/* Verify data files. */
dbfiles = db_list_dbfiles(db, backup);
ok = verify_files(dbfiles, root);
list_destroy(dbfiles, pgFile_free);
/* Verify archive log files. */
arclogs = db_list_arclogs(db, backup);
ok = ok && verify_files(arclogs, root);
/* update the status to OK or BAD */
backup->status = (ok ? BACKUP_OK : BACKUP_BAD);
db_update_status(db, backup, arclogs);
list_destroy(arclogs, pgFile_free);
if (!ok)
elog(WARNING, "corrupted backup: %s", datetime);
}
static bool
verify_files(List *files, const char *root)
{
volatile bool ok = true;
ListCell *cell;
foreach (cell, files)
{
pgFile *file = lfirst(cell);
VerifyJob *job;
CHECK_FOR_INTERRUPTS();
if (!ok)
break;
job = pgut_new(VerifyJob);
job->routine = verify_routine;
job->file = file;
job->root = root;
job->ok = &ok;
job_push((Job *) job);
}
job_wait();
return ok;
}
/*
* Verify files in the backup with size or CRC.
*/
static bool
verify_file(pgFile *file, const char *root)
{
FILE *fp;
pg_crc32 crc;
size_t len;
char path[MAXPGPATH];
char buf[8291];
/* skipped or already verified file */
if (file->flags & (PGFILE_UNMODIFIED | PGFILE_VERIFIED))
return true;
/* not a file */
if (!S_ISREG(file->mode))
{
/* XXX: check if exists? */
return true;
}
elog(LOG, "verify file: %s", file->name);
/*
* Exit on error and don't mark the backup in corrupted status to
* let users retry verification.
*/
join_path_components(path, root, file->name);
if ((fp = pgut_fopen(path, "r+")) == NULL)
{
elog(WARNING, "missing file \"%s\"", path);
return false;
}
/* Does file have correct crc? */
INIT_CRC32(crc);
while ((len = fread(buf, 1, sizeof(buf), fp)) > 0)
{
CHECK_FOR_INTERRUPTS();
COMP_CRC32(crc, buf, len);
}
FIN_CRC32(crc);
/* Update crc if not calclated yet. */
if ((file->flags & PGFILE_CRC) == 0)
{
file->crc = crc;
file->flags |= PGFILE_CRC;
}
else if (file->crc != crc)
{
fclose(fp);
elog(WARNING, "corrupted file \"%s\"", path);
return false;
}
/* Flush backup files into disk */
if (fflush(fp) != 0 || fsync(fileno(fp)) != 0)
ereport(ERROR,
(errcode_errno(),
errmsg("could not flush file \"%s\": ", path)));
fclose(fp);
file->flags |= PGFILE_VERIFIED;
return true;
}

248
xlog.c
View File

@@ -1,124 +1,124 @@
/*-------------------------------------------------------------------------
*
* xlog.c: Parse WAL files.
*
* Copyright (c) 2009-2010, 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"
#define XLOG_PAGE_MAGIC_v80 0xD05C /* 8.0 */
#define XLOG_PAGE_MAGIC_v81 0xD05D /* 8.1 */
#define XLOG_PAGE_MAGIC_v82 0xD05E /* 8.2 */
#define XLOG_PAGE_MAGIC_v83 0xD062 /* 8.3 */
#define XLOG_PAGE_MAGIC_v84 0xD063 /* 8.4 */
#define XLOG_PAGE_MAGIC_v85 0xD166 /* 8.5 */
/*
* XLogLongPageHeaderData is modified in 8.3, but the layout is compatible
* except xlp_xlog_blcksz.
*/
typedef union XLogPage
{
XLogPageHeaderData header;
XLogLongPageHeaderData lheader;
char data[XLOG_BLCKSZ];
} XLogPage;
/*
* 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, int server_version)
{
FILE *fp;
XLogPage page;
uint16 xlog_page_magic;
fp = fopen(file->path, "r");
if (!fp)
return false;
if (fread(&page, 1, sizeof(page), fp) != XLOG_BLCKSZ)
{
fclose(fp);
return false;
}
fclose(fp);
/* xlog_page_magic from server version */
if (server_version < 80000)
return false; /* never happen */
else if (server_version < 80100)
xlog_page_magic = XLOG_PAGE_MAGIC_v80;
else if (server_version < 80200)
xlog_page_magic = XLOG_PAGE_MAGIC_v81;
else if (server_version < 80300)
xlog_page_magic = XLOG_PAGE_MAGIC_v82;
else if (server_version < 80400)
xlog_page_magic = XLOG_PAGE_MAGIC_v83;
else if (server_version < 80500)
xlog_page_magic = XLOG_PAGE_MAGIC_v84;
else if (server_version < 80600)
xlog_page_magic = XLOG_PAGE_MAGIC_v85;
else
return false; /* not supported */
/* check header */
if (page.header.xlp_magic != xlog_page_magic)
return false;
if ((page.header.xlp_info & ~XLP_ALL_FLAGS) != 0)
return false;
if ((page.header.xlp_info & XLP_LONG_HEADER) == 0)
return false;
if (page.lheader.xlp_seg_size != XLogSegSize)
return false;
if (server_version >= 80300 && page.lheader.xlp_xlog_blcksz != XLOG_BLCKSZ)
return false;
/*
* check size (actual file size, not backup file size)
* TODO: Support pre-compressed xlog. They might have different file sizes.
*/
if (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);
}
/*-------------------------------------------------------------------------
*
* xlog.c: Parse WAL files.
*
* Copyright (c) 2009-2010, 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"
#define XLOG_PAGE_MAGIC_v80 0xD05C /* 8.0 */
#define XLOG_PAGE_MAGIC_v81 0xD05D /* 8.1 */
#define XLOG_PAGE_MAGIC_v82 0xD05E /* 8.2 */
#define XLOG_PAGE_MAGIC_v83 0xD062 /* 8.3 */
#define XLOG_PAGE_MAGIC_v84 0xD063 /* 8.4 */
#define XLOG_PAGE_MAGIC_v85 0xD166 /* 8.5 */
/*
* XLogLongPageHeaderData is modified in 8.3, but the layout is compatible
* except xlp_xlog_blcksz.
*/
typedef union XLogPage
{
XLogPageHeaderData header;
XLogLongPageHeaderData lheader;
char data[XLOG_BLCKSZ];
} XLogPage;
/*
* 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, int server_version)
{
FILE *fp;
XLogPage page;
uint16 xlog_page_magic;
fp = fopen(file->path, "r");
if (!fp)
return false;
if (fread(&page, 1, sizeof(page), fp) != XLOG_BLCKSZ)
{
fclose(fp);
return false;
}
fclose(fp);
/* xlog_page_magic from server version */
if (server_version < 80000)
return false; /* never happen */
else if (server_version < 80100)
xlog_page_magic = XLOG_PAGE_MAGIC_v80;
else if (server_version < 80200)
xlog_page_magic = XLOG_PAGE_MAGIC_v81;
else if (server_version < 80300)
xlog_page_magic = XLOG_PAGE_MAGIC_v82;
else if (server_version < 80400)
xlog_page_magic = XLOG_PAGE_MAGIC_v83;
else if (server_version < 80500)
xlog_page_magic = XLOG_PAGE_MAGIC_v84;
else if (server_version < 80600)
xlog_page_magic = XLOG_PAGE_MAGIC_v85;
else
return false; /* not supported */
/* check header */
if (page.header.xlp_magic != xlog_page_magic)
return false;
if ((page.header.xlp_info & ~XLP_ALL_FLAGS) != 0)
return false;
if ((page.header.xlp_info & XLP_LONG_HEADER) == 0)
return false;
if (page.lheader.xlp_seg_size != XLogSegSize)
return false;
if (server_version >= 80300 && page.lheader.xlp_xlog_blcksz != XLOG_BLCKSZ)
return false;
/*
* check size (actual file size, not backup file size)
* TODO: Support pre-compressed xlog. They might have different file sizes.
*/
if (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);
}