You've already forked pg_probackup
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:
2
Makefile
2
Makefile
@@ -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
64
SPECS/pg_rman90.spec
Normal 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
|
||||
|
312
clean.c
Normal file
312
clean.c
Normal 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(¤t))
|
||||
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
|
729
db.c
Normal file
729
db.c
Normal 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
470
delete.c
@@ -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;
|
||||
}
|
||||
|
705
file.c
Normal file
705
file.c
Normal 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
312
init.c
@@ -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
379
parray.c
@@ -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);
|
||||
}
|
||||
|
||||
|
69
parray.h
69
parray.h
@@ -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
93
pg_crc.c
Normal 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
242
pg_ctl.c
Normal 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
606
pg_rman.c
@@ -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" , ¤t.with_serverlog , SOURCE_ENV },
|
||||
{ 'b', 'Z', "compress-data" , ¤t.compress_data , SOURCE_ENV },
|
||||
{ 'b', 'C', "smooth-checkpoint" , &smooth_checkpoint , SOURCE_ENV },
|
||||
/* options with only long name (keep-xxx) */
|
||||
{ 'i', 1, "keep-data-generations" , &keep_data_generations, SOURCE_ENV },
|
||||
{ 'i', 2, "keep-data-days" , &keep_data_days , SOURCE_ENV },
|
||||
{ 'i', 3, "keep-arclog-files" , &keep_arclog_files , SOURCE_ENV },
|
||||
{ 'i', 4, "keep-arclog-days" , &keep_arclog_days , SOURCE_ENV },
|
||||
{ 'i', 5, "keep-srvlog-files" , &keep_srvlog_files , SOURCE_ENV },
|
||||
{ 'i', 6, "keep-srvlog-days" , &keep_srvlog_days , SOURCE_ENV },
|
||||
/* restore options */
|
||||
{ 's', 7, "recovery-target-time" , &target_time , SOURCE_ENV },
|
||||
{ 's', 8, "recovery-target-xid" , &target_xid , SOURCE_ENV },
|
||||
{ 's', 9, "recovery-target-inclusive" , &target_inclusive , SOURCE_ENV },
|
||||
{ 'u', 10, "recovery-target-timeline" , &target_tli , SOURCE_ENV },
|
||||
/* catalog options */
|
||||
{ 'b', 'a', "show-all" , &show_all },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
/*
|
||||
* Entry point of pg_rman command.
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
const char *cmd = NULL;
|
||||
const char *range1 = NULL;
|
||||
const char *range2 = NULL;
|
||||
bool show_timeline = false;
|
||||
pgBackupRange range;
|
||||
int i;
|
||||
|
||||
/* do not buffer progress messages */
|
||||
setvbuf(stdout, 0, _IONBF, 0); /* TODO: remove this */
|
||||
|
||||
/* initialize configuration */
|
||||
catalog_init_config(¤t);
|
||||
|
||||
/* overwrite configuration with command line arguments */
|
||||
i = pgut_getopt(argc, argv, options);
|
||||
|
||||
for (; i < argc; i++)
|
||||
{
|
||||
if (cmd == NULL)
|
||||
cmd = argv[i];
|
||||
else if (pg_strcasecmp(argv[i], "timeline") == 0 &&
|
||||
pg_strcasecmp(cmd, "show") == 0)
|
||||
show_timeline = true;
|
||||
else if (range1 == NULL)
|
||||
range1 = argv[i];
|
||||
else if (range2 == NULL)
|
||||
range2 = argv[i];
|
||||
else
|
||||
elog(ERROR_ARGS, "too many arguments");
|
||||
}
|
||||
|
||||
/* command argument (backup/restore/show/...) is required. */
|
||||
if (cmd == NULL)
|
||||
{
|
||||
help(false);
|
||||
return HELP;
|
||||
}
|
||||
|
||||
/* get object range argument if any */
|
||||
if (range1 && range2)
|
||||
parse_range(&range, range1, range2);
|
||||
else if (range1)
|
||||
parse_range(&range, range1, "");
|
||||
else
|
||||
range.begin = range.end = 0;
|
||||
|
||||
/* Read default configuration from file. */
|
||||
if (backup_path)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
|
||||
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" , ¤t.with_serverlog , SOURCE_ENV },
|
||||
{ 'b', 'Z', "compress-data" , ¤t.compress_data , SOURCE_ENV },
|
||||
{ 'b', 'C', "smooth-checkpoint" , &smooth_checkpoint , SOURCE_ENV },
|
||||
/* options with only long name (keep-xxx) */
|
||||
{ 'i', 1, "keep-data-generations" , &keep_data_generations, SOURCE_ENV },
|
||||
{ 'i', 2, "keep-data-days" , &keep_data_days , SOURCE_ENV },
|
||||
{ 'i', 3, "keep-arclog-files" , &keep_arclog_files , SOURCE_ENV },
|
||||
{ 'i', 4, "keep-arclog-days" , &keep_arclog_days , SOURCE_ENV },
|
||||
{ 'i', 5, "keep-srvlog-files" , &keep_srvlog_files , SOURCE_ENV },
|
||||
{ 'i', 6, "keep-srvlog-days" , &keep_srvlog_days , SOURCE_ENV },
|
||||
/* restore options */
|
||||
{ 's', 7, "recovery-target-time" , &target_time , SOURCE_ENV },
|
||||
{ 's', 8, "recovery-target-xid" , &target_xid , SOURCE_ENV },
|
||||
{ 's', 9, "recovery-target-inclusive" , &target_inclusive , SOURCE_ENV },
|
||||
{ 'u', 10, "recovery-target-timeline" , &target_tli , SOURCE_ENV },
|
||||
/* catalog options */
|
||||
{ 'b', 'a', "show-all" , &show_all },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
/*
|
||||
* Entry point of pg_rman command.
|
||||
*/
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
const char *cmd = NULL;
|
||||
const char *range1 = NULL;
|
||||
const char *range2 = NULL;
|
||||
bool show_timeline = false;
|
||||
pgBackupRange range;
|
||||
int i;
|
||||
|
||||
/* do not buffer progress messages */
|
||||
setvbuf(stdout, 0, _IONBF, 0); /* TODO: remove this */
|
||||
|
||||
/* initialize configuration */
|
||||
catalog_init_config(¤t);
|
||||
|
||||
/* overwrite configuration with command line arguments */
|
||||
i = pgut_getopt(argc, argv, options);
|
||||
|
||||
for (; i < argc; i++)
|
||||
{
|
||||
if (cmd == NULL)
|
||||
cmd = argv[i];
|
||||
else if (pg_strcasecmp(argv[i], "timeline") == 0 &&
|
||||
pg_strcasecmp(cmd, "show") == 0)
|
||||
show_timeline = true;
|
||||
else if (range1 == NULL)
|
||||
range1 = argv[i];
|
||||
else if (range2 == NULL)
|
||||
range2 = argv[i];
|
||||
else
|
||||
elog(ERROR_ARGS, "too many arguments");
|
||||
}
|
||||
|
||||
/* command argument (backup/restore/show/...) is required. */
|
||||
if (cmd == NULL)
|
||||
{
|
||||
help(false);
|
||||
return HELP;
|
||||
}
|
||||
|
||||
/* get object range argument if any */
|
||||
if (range1 && range2)
|
||||
parse_range(&range, range1, range2);
|
||||
else if (range1)
|
||||
parse_range(&range, range1, "");
|
||||
else
|
||||
range.begin = range.end = 0;
|
||||
|
||||
/* Read default configuration from file. */
|
||||
if (backup_path)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
|
||||
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
593
pg_rman.h
@@ -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 */
|
||||
|
386
pgut/pgut-port.c
386
pgut/pgut-port.c
@@ -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
|
||||
|
100
pgut/pgut-port.h
100
pgut/pgut-port.h
@@ -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 */
|
||||
|
3406
pgut/pgut.c
3406
pgut/pgut.c
File diff suppressed because it is too large
Load Diff
506
pgut/pgut.h
506
pgut/pgut.h
@@ -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
170
queue.c
Normal 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;
|
||||
}
|
306
script/snapshot_script_lvm.sh
Normal file
306
script/snapshot_script_lvm.sh
Normal 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
510
show.c
@@ -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
164
util.c
@@ -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
486
utils.c
Normal 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
|
370
validate.c
370
validate.c
@@ -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
184
verify.c
Normal 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
248
xlog.c
@@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user