mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-24 15:23:03 +01:00
f58c769c37
PandoraBox* pbox = reinterpret_cast<PandoraBox*>(&can_of_worms); pbox->open(); pbox->flush(); Nickolay may want to undo my ods.h changes if gcc insists in its crusade against non-PODs and poodles.
1190 lines
35 KiB
C++
1190 lines
35 KiB
C++
/*
|
|
* PROGRAM: JRD Access Method
|
|
* MODULE: nbak.cpp
|
|
* DESCRIPTION: New backup technology
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
* You may obtain a copy of the Licence at
|
|
* http://www.gnu.org/licences/lgpl.html
|
|
*
|
|
* As a special exception this file can also be included in modules
|
|
* with other source code as long as that source code has been
|
|
* released under an Open Source Initiative certificed licence.
|
|
* More information about OSI certification can be found at:
|
|
* http://www.opensource.org
|
|
*
|
|
* This module is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public Licence for more details.
|
|
*
|
|
* This module was created by members of the firebird development
|
|
* team. All individual contributions remain the Copyright (C) of
|
|
* those individuals and all rights are reserved. Contributors to
|
|
* this file are either listed below or can be obtained from a CVS
|
|
* history command.
|
|
*
|
|
* Created by: Nickolay Samofatov <skidder@bssys.com>
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
*
|
|
* $Id: nbak.cpp,v 1.27 2004-03-18 05:55:24 robocop Exp $
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "jrd.h"
|
|
#include "ods.h"
|
|
#include "lck.h"
|
|
#include "cch.h"
|
|
#include "lck_proto.h"
|
|
#include "pag_proto.h"
|
|
#include "err_proto.h"
|
|
#include "cch_proto.h"
|
|
#include "isc_proto.h"
|
|
#include "thd_proto.h"
|
|
#include "os/pio_proto.h"
|
|
#include "gen/iberror.h"
|
|
#include "gds_proto.h"
|
|
#include "os/guid.h"
|
|
#include "sch_proto.h"
|
|
#include "os/isc_i_proto.h"
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef NBAK_DEBUG
|
|
IMPLEMENT_TRACE_ROUTINE(nbak_trace, "NBAK");
|
|
#endif
|
|
|
|
/******************************** LOCK FUNCTIONS ******************************/
|
|
|
|
#ifndef SUPERSERVER
|
|
void BackupManager::increment_diff_use_count() throw()
|
|
{
|
|
// This functions are used in CS builds to prevent closing of
|
|
// difference file while there are still unflushed pages from it
|
|
// and status already changed. Backup status does not change
|
|
// externally in SS builds so we have no point to track this usage
|
|
// except for debugging purposes
|
|
NBAK_TRACE(("increment_diff_use_count %d", diff_use_count));
|
|
diff_use_count++;
|
|
}
|
|
|
|
void BackupManager::decrement_diff_use_count() throw()
|
|
{
|
|
NBAK_TRACE(("decrement_diff_use_count %d", diff_use_count));
|
|
diff_use_count--;
|
|
}
|
|
#endif
|
|
|
|
bool BackupManager::get_sw_database_lock(bool enable_signals) throw()
|
|
{
|
|
#ifdef SUPERSERVER
|
|
#ifdef WIN_NT
|
|
NBAK_TRACE(("get_sw_database_lock %d", database_lock->getState()));
|
|
#else
|
|
NBAK_TRACE(("get_sw_database_lock"));
|
|
#endif
|
|
database_lock->beginRead();
|
|
return true;
|
|
#else
|
|
NBAK_TRACE(("get_sw_database_lock %d", database_use_count));
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
database_use_count++;
|
|
if (enable_signals)
|
|
LCK_ast_enable();
|
|
if (database_lock->lck_physical >= LCK_SW)
|
|
return true;
|
|
if (!LCK_lock(tdbb, database_lock, LCK_SW, LCK_WAIT)) {
|
|
gds__log("Cannot take SW lock on database");
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void BackupManager::release_sw_database_lock() throw()
|
|
{
|
|
#ifdef SUPERSERVER
|
|
#ifdef WIN_NT
|
|
NBAK_TRACE(("release_sw_database_lock %d", database_lock->getState()));
|
|
#else
|
|
NBAK_TRACE(("release_sw_database_lock"));
|
|
#endif
|
|
database_lock->endRead();
|
|
#else
|
|
NBAK_TRACE(("release_sw_database_lock %d", database_use_count));
|
|
fb_assert(database_use_count > 0);
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
database_use_count--;
|
|
if (ast_flags & NBAK_database_blocking) {
|
|
LCK_release(tdbb, database_lock);
|
|
ast_flags &= ~NBAK_database_blocking;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void BackupManager::lock_state_write(bool thread_exit)
|
|
{
|
|
#ifdef SUPERSERVER
|
|
if (thread_exit)
|
|
THREAD_EXIT;
|
|
state_lock->beginWrite();
|
|
if (thread_exit)
|
|
THREAD_ENTER;
|
|
#else
|
|
fb_assert(!(flags & NBAK_state_in_use));
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
flags |= NBAK_state_in_use;
|
|
bool locked = false;
|
|
// Release shared lock to prevent possible deadlocks
|
|
if (state_lock->lck_physical != LCK_none)
|
|
if (LCK_convert(tdbb, state_lock, LCK_EX, LCK_NO_WAIT))
|
|
locked = true;
|
|
else {
|
|
LCK_release(tdbb, state_lock);
|
|
ast_flags &= ~NBAK_state_blocking;
|
|
backup_state = nbak_state_unknown;
|
|
}
|
|
if (!locked && !LCK_lock(tdbb, state_lock, LCK_EX, LCK_WAIT)) {
|
|
flags &= ~NBAK_state_in_use; // Lock should be released at this point
|
|
gds__log("Cannot lock database backup state for writing");
|
|
// This is OK because state changing code expect it
|
|
ERR_post(isc_lock_conflict, 0);
|
|
}
|
|
NBAK_TRACE(("backup state locked for writing"));
|
|
#endif
|
|
if (!actualize_state()) {
|
|
unlock_state_write();
|
|
ERR_punt();
|
|
}
|
|
}
|
|
|
|
bool BackupManager::try_lock_state_write()
|
|
{
|
|
#ifdef SUPERSERVER
|
|
if (!state_lock->tryBeginWrite())
|
|
return false;
|
|
#else
|
|
fb_assert(!(flags & NBAK_state_in_use));
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
flags |= NBAK_state_in_use;
|
|
bool result;
|
|
if (state_lock->lck_physical == LCK_none)
|
|
result = LCK_lock(tdbb, state_lock, LCK_EX, LCK_NO_WAIT);
|
|
else
|
|
result = LCK_convert(tdbb, state_lock, LCK_EX, LCK_NO_WAIT);
|
|
if (!result) {
|
|
flags &= ~NBAK_state_in_use;
|
|
// This code is ok only because ASTs are delivered only once per request
|
|
if (ast_flags & NBAK_state_blocking) {
|
|
LCK_release(tdbb, state_lock);
|
|
ast_flags &= ~NBAK_state_blocking;
|
|
backup_state = nbak_state_unknown;
|
|
}
|
|
return false;
|
|
}
|
|
NBAK_TRACE(("backup state locked for writing"));
|
|
#endif
|
|
if (!actualize_state()) {
|
|
unlock_state_write();
|
|
ERR_punt();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BackupManager::unlock_state_write() throw()
|
|
{
|
|
#ifdef SUPERSERVER
|
|
state_lock->endWrite();
|
|
#else
|
|
fb_assert(flags & NBAK_state_in_use);
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
// ASTs are going to be reposted after CONVERT
|
|
ast_flags &= ~NBAK_state_blocking;
|
|
LCK_convert(tdbb, state_lock, LCK_SR, LCK_WAIT);
|
|
flags &= ~NBAK_state_in_use;
|
|
if (ast_flags & NBAK_state_blocking) {
|
|
LCK_release(tdbb, state_lock);
|
|
ast_flags &= ~NBAK_state_blocking;
|
|
backup_state = nbak_state_unknown;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool BackupManager::lock_alloc_write(bool thread_exit) throw()
|
|
{
|
|
#ifdef SUPERSERVER
|
|
if (thread_exit)
|
|
THREAD_EXIT;
|
|
alloc_lock->beginWrite();
|
|
if (thread_exit)
|
|
THREAD_ENTER;
|
|
#else
|
|
fb_assert(!(flags & NBAK_alloc_in_use));
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
flags |= NBAK_alloc_in_use;
|
|
// Release shared lock to prevent possible deadlocks
|
|
bool locked = false;
|
|
if (alloc_lock->lck_physical != LCK_none)
|
|
if (LCK_convert(tdbb, alloc_lock, LCK_EX, LCK_NO_WAIT)) {
|
|
locked = true;
|
|
}
|
|
else {
|
|
LCK_release(tdbb, alloc_lock);
|
|
ast_flags &= ~NBAK_alloc_blocking;
|
|
ast_flags |= NBAK_alloc_dirty;
|
|
}
|
|
if (!locked && !LCK_lock(tdbb, alloc_lock, LCK_EX, LCK_WAIT)) {
|
|
flags &= ~NBAK_alloc_in_use;
|
|
gds__log("Cannot lock backup allocation table for writing");
|
|
return false;
|
|
}
|
|
#endif
|
|
if (!actualize_alloc()) {
|
|
unlock_alloc_write();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BackupManager::unlock_alloc_write() throw()
|
|
{
|
|
#ifdef SUPERSERVER
|
|
alloc_lock->endWrite();
|
|
#else
|
|
fb_assert(flags & NBAK_alloc_in_use);
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
// ASTs are going to be reposted after CONVERT
|
|
ast_flags &= ~NBAK_alloc_blocking;
|
|
LCK_convert(tdbb, alloc_lock, LCK_SR, LCK_WAIT);
|
|
flags &= ~NBAK_alloc_in_use;
|
|
if (ast_flags & NBAK_alloc_blocking) {
|
|
LCK_release(tdbb, alloc_lock);
|
|
ast_flags &= ~NBAK_alloc_blocking;
|
|
ast_flags |= NBAK_alloc_dirty;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool BackupManager::lock_state(bool thread_exit) throw()
|
|
{
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
// If we own exlock here no need to do anything else
|
|
if (tdbb->tdbb_flags & TDBB_set_backup_state)
|
|
return true;
|
|
#ifdef SUPERSERVER
|
|
if (thread_exit)
|
|
THREAD_EXIT;
|
|
state_lock->beginRead();
|
|
if (thread_exit)
|
|
THREAD_ENTER;
|
|
#else
|
|
fb_assert(!(flags & NBAK_state_in_use));
|
|
flags |= NBAK_state_in_use;
|
|
if (state_lock->lck_physical < LCK_SR) {
|
|
if (!LCK_lock(tdbb, state_lock, LCK_SR, LCK_WAIT)) {
|
|
flags &= ~NBAK_state_in_use;
|
|
gds__log("Cannot lock database backup state for reading");
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
if (!actualize_state()) {
|
|
unlock_state();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BackupManager::unlock_state() throw()
|
|
{
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
// If we own exlock here no need to do anything else
|
|
if (tdbb->tdbb_flags & TDBB_set_backup_state)
|
|
return;
|
|
#ifdef SUPERSERVER
|
|
state_lock->endRead();
|
|
#else
|
|
fb_assert(flags & NBAK_state_in_use);
|
|
flags &= ~NBAK_state_in_use;
|
|
if (ast_flags & NBAK_state_blocking) {
|
|
LCK_release(tdbb, state_lock);
|
|
ast_flags &= ~NBAK_state_blocking;
|
|
backup_state = nbak_state_unknown;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool BackupManager::lock_alloc(bool thread_exit) throw() {
|
|
#ifdef SUPERSERVER
|
|
if (thread_exit)
|
|
THREAD_EXIT;
|
|
alloc_lock->beginRead();
|
|
if (thread_exit)
|
|
THREAD_ENTER;
|
|
#else
|
|
fb_assert(!(flags & NBAK_alloc_in_use));
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
flags |= NBAK_alloc_in_use;
|
|
if (alloc_lock->lck_physical < LCK_SR) {
|
|
if (!LCK_lock(tdbb, alloc_lock, LCK_SR, LCK_WAIT)) {
|
|
flags &= ~NBAK_alloc_in_use;
|
|
gds__log("Cannot lock backup allocation table for reading");
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
if (!actualize_alloc()) {
|
|
unlock_alloc();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BackupManager::unlock_alloc() throw() {
|
|
#ifdef SUPERSERVER
|
|
alloc_lock->endRead();
|
|
#else
|
|
fb_assert(flags & NBAK_alloc_in_use);
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
flags &= ~NBAK_alloc_in_use;
|
|
if (ast_flags & NBAK_alloc_blocking) {
|
|
LCK_release(tdbb, alloc_lock);
|
|
ast_flags &= ~NBAK_alloc_blocking;
|
|
ast_flags |= NBAK_alloc_dirty;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/********************************* AST FUNCTIONS ******************************/
|
|
|
|
#ifndef SUPERSERVER
|
|
|
|
int BackupManager::backup_state_ast(void *ast_object) throw()
|
|
{
|
|
/**************************************
|
|
*
|
|
* b a c k u p _ s t a t e _ a s t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A blocking AST has been issued to give up
|
|
* the lock on the backup state semaphore.
|
|
* Do so if we are not using backup state.
|
|
* Otherwise set blocking flag and blocking routine
|
|
* will release the lock as soon it finishes
|
|
*
|
|
**************************************/
|
|
Database* new_dbb = static_cast<Database*>(ast_object);
|
|
Lock* lock = new_dbb->backup_manager->state_lock;
|
|
|
|
ISC_ast_enter();
|
|
|
|
/* Since this routine will be called asynchronously, we must establish
|
|
a thread context. */
|
|
|
|
thread_db thd_context, *tdbb;
|
|
SET_THREAD_DATA;
|
|
|
|
tdbb->tdbb_database = new_dbb;
|
|
tdbb->tdbb_attachment = lock->lck_attachment;
|
|
tdbb->tdbb_quantum = QUANTUM;
|
|
tdbb->tdbb_request = NULL;
|
|
tdbb->tdbb_transaction = NULL;
|
|
|
|
NBAK_TRACE_AST("NBAK, backup_state_ast");
|
|
|
|
if (new_dbb->backup_manager->flags & NBAK_state_in_use)
|
|
new_dbb->backup_manager->ast_flags |= NBAK_state_blocking;
|
|
else {
|
|
// We know state only as long we lock it
|
|
new_dbb->backup_manager->backup_state = nbak_state_unknown;
|
|
LCK_release(tdbb, lock);
|
|
}
|
|
|
|
/* Restore the prior thread context */
|
|
|
|
RESTORE_THREAD_DATA;
|
|
|
|
ISC_ast_exit();
|
|
return 0;
|
|
}
|
|
|
|
int BackupManager::alloc_table_ast(void *ast_object) throw()
|
|
{
|
|
/**************************************
|
|
*
|
|
* a l l o c _ t a b l e _ a s t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A blocking AST has been issued to give up
|
|
* the lock on the allocate table semaphore.
|
|
* Do so if we are not using allocation table.
|
|
* Otherwise set blocking flag and blocking routine
|
|
* will release the lock as soon it finishes
|
|
*
|
|
**************************************/
|
|
Database* new_dbb = static_cast<Database*>(ast_object);
|
|
Lock* lock = new_dbb->backup_manager->alloc_lock;
|
|
|
|
ISC_ast_enter();
|
|
|
|
/* Since this routine will be called asynchronously, we must establish
|
|
a thread context. */
|
|
|
|
thread_db thd_context, *tdbb;
|
|
SET_THREAD_DATA;
|
|
|
|
tdbb->tdbb_database = new_dbb;
|
|
tdbb->tdbb_attachment = lock->lck_attachment;
|
|
tdbb->tdbb_quantum = QUANTUM;
|
|
tdbb->tdbb_request = NULL;
|
|
tdbb->tdbb_transaction = NULL;
|
|
|
|
NBAK_TRACE_AST("NBAK, alloc_table_ast");
|
|
|
|
if (new_dbb->backup_manager->flags & NBAK_alloc_in_use)
|
|
new_dbb->backup_manager->ast_flags |= NBAK_alloc_blocking;
|
|
else {
|
|
new_dbb->backup_manager->ast_flags |= NBAK_alloc_dirty;
|
|
LCK_release(tdbb, lock);
|
|
}
|
|
|
|
/* Restore the prior thread context */
|
|
|
|
RESTORE_THREAD_DATA;
|
|
|
|
ISC_ast_exit();
|
|
return 0;
|
|
}
|
|
|
|
int BackupManager::backup_database_ast(void *ast_object) throw()
|
|
{
|
|
/**************************************
|
|
*
|
|
* b a c k u p _ d a t a b a s e _ a s t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A blocking AST has been issued to give up
|
|
* the lock on the backup state semaphore.
|
|
* Do so if we are not using backup state.
|
|
* Otherwise set blocking flag and blocking routine
|
|
* will release the lock as soon it finishes
|
|
*
|
|
**************************************/
|
|
Database* new_dbb = static_cast<Database*>(ast_object);
|
|
ISC_STATUS_ARRAY ast_status;
|
|
|
|
Lock* lock = new_dbb->backup_manager->database_lock;
|
|
|
|
ISC_ast_enter();
|
|
|
|
/* Since this routine will be called asynchronously, we must establish
|
|
a thread context. */
|
|
|
|
thread_db thd_context, *tdbb;
|
|
SET_THREAD_DATA;
|
|
|
|
tdbb->tdbb_database = new_dbb;
|
|
tdbb->tdbb_attachment = lock->lck_attachment;
|
|
tdbb->tdbb_quantum = QUANTUM;
|
|
tdbb->tdbb_request = NULL;
|
|
tdbb->tdbb_transaction = NULL;
|
|
tdbb->tdbb_status_vector = ast_status;
|
|
|
|
NBAK_TRACE_AST("NBAK, backup_database_ast");
|
|
|
|
if (new_dbb->backup_manager->database_use_count) {
|
|
new_dbb->backup_manager->ast_flags |= NBAK_database_blocking;
|
|
ast_status[1] = 0;
|
|
CCH_flush_database(tdbb); // This may release database lock
|
|
if (ast_status[1])
|
|
gds__log_status(new_dbb->dbb_file->fil_string, ast_status);
|
|
}
|
|
|
|
/* Restore the prior thread context */
|
|
|
|
RESTORE_THREAD_DATA;
|
|
|
|
ISC_ast_exit();
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/********************************** CORE LOGIC ********************************/
|
|
|
|
void BackupManager::generate_filename() throw()
|
|
{
|
|
strncpy(diff_name, (char*)database->dbb_filename.c_str(), sizeof(diff_name));
|
|
strncat(diff_name, ".delta", sizeof(diff_name)-strlen(diff_name)-1);
|
|
}
|
|
|
|
// Initialize and open difference file for writing
|
|
void BackupManager::begin_backup()
|
|
{
|
|
NBAK_TRACE(("begin_backup"));
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
|
|
// Lock header page first to prevent possible deadlock
|
|
WIN window(HEADER_PAGE);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
bool state_locked = false, header_locked = true;
|
|
try {
|
|
lock_state_write(true);
|
|
state_locked = true;
|
|
NBAK_TRACE(("state locked"));
|
|
|
|
// Check state
|
|
if (backup_state != nbak_state_normal) {
|
|
NBAK_TRACE(("end backup - invalid state %d", backup_state));
|
|
unlock_state_write();
|
|
CCH_RELEASE(tdbb, &window);
|
|
return;
|
|
}
|
|
// Create file
|
|
NBAK_TRACE(("Creating difference file %s", diff_name));
|
|
diff_file = PIO_create(database, diff_name, strlen(diff_name), true);
|
|
// Zero out first page (empty allocation table)
|
|
BufferDesc temp_bdb;
|
|
temp_bdb.bdb_page = 0;
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = reinterpret_cast<PAG>(alloc_buffer);
|
|
memset(alloc_buffer, 0, database->dbb_page_size);
|
|
if (!PIO_write(diff_file, &temp_bdb, temp_bdb.bdb_buffer, tdbb->tdbb_status_vector))
|
|
ERR_punt();
|
|
NBAK_TRACE(("Set backup state in header"));
|
|
FB_GUID guid;
|
|
GenerateGuid(&guid);
|
|
tdbb->tdbb_flags |= TDBB_set_backup_state;
|
|
// Set state in database header page. All changes are written to main database file yet.
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
int newState = nbak_state_stalled;
|
|
header->hdr_flags = (header->hdr_flags & ~hdr_backup_mask) | newState;
|
|
// This number may be smaller than actual because some pages may be not flushed to
|
|
// disk yet. This is not a problem as it can cause only a slight performance degradation
|
|
backup_pages = header->hdr_backup_pages = PIO_act_alloc(database);
|
|
const ULONG adjusted_scn = ++header->pag_scn; // Generate new SCN
|
|
PAG_replace_entry_first(header, HDR_backup_guid, sizeof(guid),
|
|
reinterpret_cast<const UCHAR*>(&guid));
|
|
|
|
header_locked = false;
|
|
CCH_RELEASE(tdbb, &window);
|
|
tdbb->tdbb_flags &= ~TDBB_set_backup_state;
|
|
|
|
backup_state = newState;
|
|
current_scn = adjusted_scn;
|
|
// This is essential for SuperServer to prevent multi-threading issues
|
|
// We already modified state in header, error here is not very important
|
|
#ifdef SUPERSERVER
|
|
// We cannot do this in classic because we do not have alloc_lock now
|
|
// and we'll not get alloc_table_ast at the appropriate point
|
|
actualize_alloc();
|
|
#endif
|
|
unlock_state_write();
|
|
} catch (const std::exception&) {
|
|
backup_state = nbak_state_unknown;
|
|
tdbb->tdbb_flags &= ~TDBB_set_backup_state;
|
|
if (state_locked)
|
|
unlock_state_write();
|
|
if (header_locked)
|
|
CCH_RELEASE(tdbb, &window);
|
|
throw;
|
|
}
|
|
// Flush local cache
|
|
CCH_flush_database(tdbb);
|
|
#ifdef SUPERSERVER
|
|
// Check if we can take lock on main database file
|
|
if (!database_lock->tryBeginWrite()) {
|
|
ERR_bugcheck_msg("Internal error. Cannot take EX lock on database");
|
|
}
|
|
database_lock->endWrite();
|
|
#else
|
|
if (database_use_count) {
|
|
ERR_bugcheck_msg("Internal error. Cannot take EX lock on database when flushing local cache");
|
|
}
|
|
// Send AST's to flush other caches
|
|
database_use_count++;
|
|
if (!LCK_lock(tdbb, database_lock, LCK_EX, LCK_WAIT)) {
|
|
ERR_bugcheck_msg(
|
|
"Internal error. Cannot take EX lock on database when flushing other processes cache");
|
|
}
|
|
database_use_count--;
|
|
LCK_release(tdbb, database_lock);
|
|
#endif
|
|
}
|
|
|
|
// Merge difference file to main files (if needed) and unlink() difference
|
|
// file then. If merge is already in progress method silently returns and
|
|
// does nothing (so it can be used for recovery on database startup).
|
|
void BackupManager::end_backup(bool recover) {
|
|
NBAK_TRACE(("end_backup"));
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
ULONG adjusted_scn; // We use this value to prevent race conditions.
|
|
// They are possible because we release state lock
|
|
// for some instants and anything is possible at
|
|
// that times.
|
|
|
|
// Lock header page first to prevent possible deadlock
|
|
WIN window(HEADER_PAGE);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
bool state_locked = false, header_locked = true;
|
|
|
|
try {
|
|
if (recover) {
|
|
if (!try_lock_state_write()) {
|
|
CCH_RELEASE(tdbb, &window);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
lock_state_write(true);
|
|
state_locked = true;
|
|
NBAK_TRACE(("state locked"));
|
|
// Check state
|
|
NBAK_TRACE(("Backup state at start of end_backup is %d", backup_state));
|
|
if (backup_state == nbak_state_normal || (recover && backup_state != nbak_state_merge))
|
|
{
|
|
NBAK_TRACE(("invalid state %d", backup_state));
|
|
unlock_state_write();
|
|
CCH_RELEASE(tdbb, &window);
|
|
return;
|
|
}
|
|
NBAK_TRACE(("difference file %s", diff_name));
|
|
// Set state in database header
|
|
tdbb->tdbb_flags |= TDBB_set_backup_state;
|
|
NBAK_TRACE(("Current backup state is %d", backup_state));
|
|
backup_state = nbak_state_merge;
|
|
adjusted_scn = ++current_scn;
|
|
NBAK_TRACE(("New state is getting to become %d", backup_state));
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
NBAK_TRACE(("New state is getting to become after fetches %d", backup_state));
|
|
// Generate new SCN
|
|
header->pag_scn = current_scn;
|
|
NBAK_TRACE(("new SCN=%d is getting written to header", adjusted_scn));
|
|
// Adjust state
|
|
header->hdr_flags = (header->hdr_flags & ~hdr_backup_mask) | backup_state;
|
|
header_locked = false;
|
|
CCH_RELEASE(tdbb, &window);
|
|
tdbb->tdbb_flags &= ~TDBB_set_backup_state;
|
|
NBAK_TRACE(("Set state %d in header page", backup_state));
|
|
} catch (const std::exception&) {
|
|
backup_state = nbak_state_unknown;
|
|
tdbb->tdbb_flags &= ~TDBB_set_backup_state;
|
|
if (state_locked)
|
|
unlock_state_write();
|
|
if (header_locked)
|
|
CCH_RELEASE(tdbb, &window);
|
|
throw;
|
|
}
|
|
|
|
|
|
// Here comes the dirty work. We need to reapply all changes from difference file to database
|
|
|
|
// Release write state lock and get read lock.
|
|
// Merge process should not inhibit normal operations.
|
|
tdbb->tdbb_flags &= ~TDBB_set_backup_state;
|
|
unlock_state_write();
|
|
if (!lock_state(true))
|
|
ERR_punt();
|
|
try {
|
|
NBAK_TRACE(("Merge. State=%d, current_scn=%d, adjusted_scn=%d",
|
|
backup_state, current_scn, adjusted_scn));
|
|
if (backup_state != nbak_state_merge || current_scn != adjusted_scn) {
|
|
/* Handle the case when somebody finalized merge for us */
|
|
unlock_state();
|
|
return;
|
|
}
|
|
NBAK_TRACE(("Status OK."));
|
|
if (!actualize_alloc())
|
|
ERR_punt();
|
|
NBAK_TRACE(("Allocation table %p is current.", alloc_table));
|
|
AllocItemTree::Accessor all(alloc_table);
|
|
|
|
tdbb->tdbb_flags |= TDBB_set_backup_state | TDBB_backup_merge;
|
|
|
|
if (all.getFirst()) {
|
|
do {
|
|
WIN window(all.current().db_page);
|
|
PAG page = CCH_FETCH(tdbb, &window, LCK_write, pag_undefined);
|
|
if (page->pag_scn != current_scn)
|
|
CCH_MARK(tdbb, &window);
|
|
CCH_RELEASE(tdbb, &window);
|
|
} while (all.getNext());
|
|
}
|
|
CCH_flush(tdbb, FLUSH_ALL, 0); // Really write changes to main database file
|
|
|
|
tdbb->tdbb_flags &= ~(TDBB_set_backup_state | TDBB_backup_merge);
|
|
unlock_state();
|
|
|
|
} catch(const std::exception&) {
|
|
tdbb->tdbb_flags &= ~(TDBB_set_backup_state | TDBB_backup_merge);
|
|
unlock_state();
|
|
throw;
|
|
}
|
|
|
|
// We finished. We need to reflect it in our database header page
|
|
window.win_page = HEADER_PAGE;
|
|
window.win_flags = 0;
|
|
header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
state_locked = false;
|
|
header_locked = true;
|
|
try {
|
|
lock_state_write(true);
|
|
state_locked = true;
|
|
// Check state
|
|
if (backup_state != nbak_state_merge || current_scn != adjusted_scn) {
|
|
/* Handle the case when somebody finalized merge for us */
|
|
unlock_state_write();
|
|
CCH_RELEASE(tdbb, &window);
|
|
return;
|
|
}
|
|
// Set state in database header
|
|
tdbb->tdbb_flags |= TDBB_set_backup_state;
|
|
backup_state = nbak_state_normal;
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
// Adjust state
|
|
header->hdr_flags = (header->hdr_flags & ~hdr_backup_mask) | backup_state;
|
|
NBAK_TRACE(("Set state %d in header page", backup_state));
|
|
// Generate new SCN
|
|
header->pag_scn = ++current_scn;
|
|
NBAK_TRACE(("new SCN=%d is getting written to header"));
|
|
header_locked = false;
|
|
CCH_RELEASE(tdbb, &window);
|
|
tdbb->tdbb_flags &= ~TDBB_set_backup_state;
|
|
|
|
// Page allocation table cache is no longer valid
|
|
delete alloc_table;
|
|
alloc_table = NULL;
|
|
last_allocated_page = 0;
|
|
|
|
if (diff_file) {
|
|
#ifndef SUPERSERVER
|
|
diff_generation++;
|
|
#endif
|
|
PIO_close(diff_file);
|
|
diff_file = NULL;
|
|
}
|
|
unlink(diff_name);
|
|
|
|
unlock_state_write();
|
|
NBAK_TRACE(("backup ended"));
|
|
} catch (const std::exception&) {
|
|
backup_state = nbak_state_unknown;
|
|
tdbb->tdbb_flags &= ~TDBB_set_backup_state;
|
|
if (state_locked)
|
|
unlock_state_write();
|
|
if (header_locked)
|
|
CCH_RELEASE(tdbb, &window);
|
|
throw;
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool BackupManager::actualize_alloc() throw()
|
|
{
|
|
if (alloc_table
|
|
#ifndef SUPERSERVER
|
|
&& !(ast_flags & NBAK_alloc_dirty)
|
|
#endif
|
|
) return true;
|
|
ISC_STATUS *status_vector = GET_THREAD_DATA->tdbb_status_vector;
|
|
try {
|
|
NBAK_TRACE(("actualize_alloc last_allocated_page=%d alloc_table=%p",
|
|
last_allocated_page, alloc_table));
|
|
// For SuperServer this routine is really executed only at database startup when
|
|
// it has exlock or when exclusive access to database is enabled
|
|
if (!alloc_table)
|
|
alloc_table = FB_NEW(*database->dbb_permanent) AllocItemTree(database->dbb_permanent);
|
|
while (true) {
|
|
BufferDesc temp_bdb;
|
|
// Difference file pointer pages have one ULONG as number of pages allocated on the page and
|
|
// then go physical numbers of pages from main database file. Offsets of numbers correspond
|
|
// to difference file pages.
|
|
|
|
// Get offset of pointer page. We can do so because page sizes are powers of 2
|
|
temp_bdb.bdb_page = last_allocated_page & ~(database->dbb_page_size/sizeof(ULONG)-1);
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = reinterpret_cast<PAG>(alloc_buffer);
|
|
|
|
if (!PIO_read(diff_file, &temp_bdb, temp_bdb.bdb_buffer, status_vector))
|
|
return false;
|
|
for (ULONG i = last_allocated_page - temp_bdb.bdb_page; i < alloc_buffer[0]; i++)
|
|
{
|
|
NBAK_TRACE(("alloc item page=%d, diff=%d", alloc_buffer[i + 1], temp_bdb.bdb_page + i + 1));
|
|
if (!alloc_table->add(AllocItem(alloc_buffer[i + 1], temp_bdb.bdb_page + i + 1)))
|
|
{
|
|
database->dbb_flags |= DBB_bugcheck;
|
|
status_vector[0] = isc_arg_gds;
|
|
status_vector[1] = isc_bug_check;
|
|
status_vector[2] = isc_arg_string;
|
|
status_vector[3] =
|
|
(ISC_STATUS)(U_IPTR) ERR_cstring("Duplicated item in allocation table detected");
|
|
status_vector[4] = isc_arg_end;
|
|
return false;
|
|
}
|
|
}
|
|
last_allocated_page = temp_bdb.bdb_page + alloc_buffer[0];
|
|
if (alloc_buffer[0] == database->dbb_page_size / sizeof(ULONG) - 1)
|
|
// if page is full adjust position for next pointer page
|
|
last_allocated_page++;
|
|
else
|
|
// We finished reading allocation table
|
|
break;
|
|
}
|
|
} catch (const std::exception& ex) {
|
|
// Handle out of memory error, etc
|
|
delete alloc_table;
|
|
Firebird::stuff_exception(status_vector, ex);
|
|
alloc_table = NULL;
|
|
last_allocated_page = 0;
|
|
return false;
|
|
}
|
|
#ifndef SUPERSERVER
|
|
ast_flags &= ~NBAK_alloc_dirty;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
// Return page index in difference file that can be used in
|
|
// write_difference call later.
|
|
ULONG BackupManager::get_page_index(ULONG db_page) const throw()
|
|
{
|
|
AllocItemTree::Accessor a(alloc_table);
|
|
if (a.locate(db_page))
|
|
return a.current().diff_page;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
// Mark next difference page as used by some database page
|
|
ULONG BackupManager::allocate_difference_page(ULONG db_page) throw() {
|
|
fb_assert(last_allocated_page % (database->dbb_page_size / sizeof(ULONG)) == alloc_buffer[0]);
|
|
|
|
ISC_STATUS* status_vector = GET_THREAD_DATA->tdbb_status_vector;
|
|
// Grow file first. This is done in such order to keep difference
|
|
// file consistent in case of write error. We should always be able
|
|
// to read next alloc page when previous one is full.
|
|
BufferDesc temp_bdb;
|
|
temp_bdb.bdb_page = last_allocated_page+1;
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = reinterpret_cast<PAG>(empty_buffer);
|
|
if (!PIO_write(diff_file, &temp_bdb, (PAG)empty_buffer, status_vector))
|
|
return 0;
|
|
|
|
const bool alloc_page_full = alloc_buffer[0] == database->dbb_page_size / sizeof(ULONG) - 2;
|
|
if (alloc_page_full) {
|
|
// Pointer page is full. Its time to create new one.
|
|
temp_bdb.bdb_page = last_allocated_page + 2;
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = reinterpret_cast<PAG>(empty_buffer);
|
|
if (!PIO_write(diff_file, &temp_bdb, (PAG)empty_buffer, status_vector))
|
|
return 0;
|
|
}
|
|
|
|
// Write new item to the allocation table
|
|
temp_bdb.bdb_page = last_allocated_page & ~(database->dbb_page_size / sizeof(ULONG) - 1);
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = reinterpret_cast<PAG>(alloc_buffer);
|
|
alloc_buffer[++alloc_buffer[0]] = db_page;
|
|
if (!PIO_write(diff_file, &temp_bdb, temp_bdb.bdb_buffer, status_vector))
|
|
return 0;
|
|
last_allocated_page++;
|
|
// Register new page in the alloc table
|
|
try {
|
|
alloc_table->add(AllocItem(db_page, last_allocated_page));
|
|
} catch(const std::exception& ex) {
|
|
// Handle out of memory error
|
|
delete alloc_table;
|
|
alloc_table = NULL;
|
|
last_allocated_page = 0;
|
|
Firebird::stuff_exception(status_vector, ex);
|
|
return 0;
|
|
}
|
|
// Adjust buffer and counters if we allocated new alloc page earlier
|
|
if (alloc_page_full) {
|
|
last_allocated_page++;
|
|
memset(alloc_buffer, 0, database->dbb_page_size);
|
|
return last_allocated_page-1;
|
|
}
|
|
|
|
return last_allocated_page;
|
|
}
|
|
|
|
bool BackupManager::write_difference(ISC_STATUS* status, ULONG diff_page, struct pag* page) throw()
|
|
{
|
|
BufferDesc temp_bdb;
|
|
temp_bdb.bdb_page = diff_page;
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = page;
|
|
if (!PIO_write(diff_file, &temp_bdb, page, status))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool BackupManager::read_difference(ULONG diff_page, struct pag* page) throw()
|
|
{
|
|
BufferDesc temp_bdb;
|
|
temp_bdb.bdb_page = diff_page;
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = page;
|
|
if (!PIO_read(diff_file, &temp_bdb, page, GET_THREAD_DATA->tdbb_status_vector))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
BackupManager::BackupManager(Database* _database, int ini_state) :
|
|
database(_database), diff_file(NULL), alloc_table(NULL),
|
|
backup_state(ini_state), last_allocated_page(0),
|
|
current_scn(0), backup_pages(0), diff_pending_close(false)
|
|
{
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
diff_name[0] = 0;
|
|
|
|
// Allocate various database page buffers needed for operation
|
|
temp_buffers_space = FB_NEW(*database->dbb_permanent) BYTE[database->dbb_page_size * 3 + MIN_PAGE_SIZE];
|
|
// Align it at sector boundary for faster IO
|
|
BYTE *temp_buffers = reinterpret_cast<BYTE*>(
|
|
FB_ALIGN(reinterpret_cast<U_IPTR>(temp_buffers_space), MIN_PAGE_SIZE));
|
|
memset(temp_buffers, 0, database->dbb_page_size * 3);
|
|
|
|
empty_buffer = reinterpret_cast<ULONG*>(temp_buffers);
|
|
spare_buffer = reinterpret_cast<ULONG*>(temp_buffers + database->dbb_page_size);
|
|
alloc_buffer = reinterpret_cast<ULONG*>(temp_buffers + database->dbb_page_size + 2);
|
|
|
|
|
|
#ifdef SUPERSERVER
|
|
alloc_lock = FB_NEW(*_database->dbb_permanent) Firebird::RWLock();
|
|
state_lock = FB_NEW(*_database->dbb_permanent) Firebird::RWLock();
|
|
database_lock = FB_NEW(*_database->dbb_permanent) Firebird::RWLock();
|
|
adjust_state_lock = FB_NEW(*_database->dbb_permanent) Firebird::Mutex();
|
|
#else
|
|
flags = 0;
|
|
ast_flags = 0;
|
|
database_use_count = 0;
|
|
diff_use_count = 0;
|
|
diff_generation = 0;
|
|
|
|
state_lock = FB_NEW_RPT(*database->dbb_permanent, 0) Lock();
|
|
state_lock->lck_type = LCK_backup_state;
|
|
state_lock->lck_owner_handle = LCK_get_owner_handle(tdbb, state_lock->lck_type);
|
|
state_lock->lck_parent = database->dbb_lock;
|
|
state_lock->lck_length = 0;
|
|
state_lock->lck_dbb = database;
|
|
state_lock->lck_object = reinterpret_cast<blk*>(database);
|
|
state_lock->lck_ast = backup_state_ast;
|
|
|
|
alloc_lock = FB_NEW_RPT(*database->dbb_permanent, 0) Lock();
|
|
alloc_lock->lck_type = LCK_backup_alloc;
|
|
alloc_lock->lck_owner_handle = LCK_get_owner_handle(tdbb, alloc_lock->lck_type);
|
|
alloc_lock->lck_parent = database->dbb_lock;
|
|
alloc_lock->lck_length = 0;
|
|
alloc_lock->lck_dbb = database;
|
|
alloc_lock->lck_object = reinterpret_cast<blk*>(database);
|
|
alloc_lock->lck_ast = alloc_table_ast;
|
|
|
|
database_lock = FB_NEW_RPT(*database->dbb_permanent, 0) Lock();
|
|
database_lock->lck_type = LCK_backup_database;
|
|
database_lock->lck_owner_handle = LCK_get_owner_handle(tdbb, database_lock->lck_type);
|
|
database_lock->lck_parent = database->dbb_lock;
|
|
database_lock->lck_length = 0;
|
|
database_lock->lck_dbb = database;
|
|
database_lock->lck_object = reinterpret_cast<blk*>(database);
|
|
database_lock->lck_ast = backup_database_ast;
|
|
#endif
|
|
}
|
|
|
|
void BackupManager::shutdown_locks() throw()
|
|
{
|
|
#ifndef SUPERSERVER
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
if (state_lock)
|
|
LCK_release(tdbb, state_lock);
|
|
if (alloc_lock)
|
|
LCK_release(tdbb, alloc_lock);
|
|
if (database_lock)
|
|
LCK_release(tdbb, database_lock);
|
|
#endif
|
|
}
|
|
|
|
|
|
BackupManager::~BackupManager()
|
|
{
|
|
if (diff_file)
|
|
PIO_close(diff_file);
|
|
shutdown_locks();
|
|
delete alloc_table;
|
|
delete[] temp_buffers_space;
|
|
#ifdef SUPERSERVER
|
|
delete alloc_lock;
|
|
delete state_lock;
|
|
delete adjust_state_lock;
|
|
delete database_lock;
|
|
#endif
|
|
}
|
|
|
|
void BackupManager::set_difference(const char* filename) {
|
|
thread_db* tdbb = GET_THREAD_DATA;
|
|
|
|
if (filename) {
|
|
WIN window(HEADER_PAGE);
|
|
header_page* header =
|
|
(header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
PAG_replace_entry_first(header, HDR_difference_file,
|
|
strlen(filename), reinterpret_cast<const UCHAR*>(filename));
|
|
CCH_RELEASE(tdbb, &window);
|
|
strncpy(diff_name, filename, sizeof(diff_name));
|
|
}
|
|
else {
|
|
PAG_delete_clump_entry(HEADER_PAGE, HDR_difference_file);
|
|
generate_filename();
|
|
}
|
|
}
|
|
|
|
bool BackupManager::actualize_state() throw() {
|
|
if (backup_state != nbak_state_unknown)
|
|
return true;
|
|
|
|
// State is unknown. We need to read it from the disk.
|
|
// We cannot use CCH for this because of likely recursion.
|
|
NBAK_TRACE(("actualize_state"));
|
|
|
|
ISC_STATUS *status = GET_THREAD_DATA->tdbb_status_vector;
|
|
|
|
// Read original page from database file or shadows.
|
|
SSHORT retryCount = 0;
|
|
header_page* header = reinterpret_cast<header_page*>(spare_buffer);
|
|
BufferDesc temp_bdb;
|
|
temp_bdb.bdb_page = HEADER_PAGE;
|
|
temp_bdb.bdb_dbb = database;
|
|
temp_bdb.bdb_buffer = header;
|
|
jrd_file* file = database->dbb_file;
|
|
while (!PIO_read(file, &temp_bdb, temp_bdb.bdb_buffer, status)) {
|
|
if (!CCH_rollover_to_shadow(database, file, false)) {
|
|
NBAK_TRACE(("Shadow change error"));
|
|
return false;
|
|
}
|
|
if (file != database->dbb_file)
|
|
file = database->dbb_file;
|
|
else {
|
|
if (retryCount++ == 3) {
|
|
NBAK_TRACE(("IO error"));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const int new_backup_state = header->hdr_flags & hdr_backup_mask;
|
|
NBAK_TRACE(("backup state read from header is %d", new_backup_state));
|
|
// Check is we missed lock/unlock cycle and need to invalidate
|
|
// our allocation table and file handle
|
|
const bool missed_cycle = (header->pag_scn - current_scn) > 1;
|
|
current_scn = header->pag_scn;
|
|
backup_pages = header->hdr_backup_pages;
|
|
|
|
// Read difference file name from header clumplets
|
|
bool fname_found = false;
|
|
const UCHAR* p = header->hdr_data;
|
|
while (true) {
|
|
switch (*p) {
|
|
case HDR_backup_guid:
|
|
p += p[1] + 2;
|
|
continue;
|
|
case HDR_difference_file:
|
|
fname_found = true;
|
|
memcpy(diff_name, p + 2, p[1]);
|
|
diff_name[p[1]] = 0;
|
|
}
|
|
break;
|
|
}
|
|
if (!fname_found)
|
|
generate_filename();
|
|
|
|
if (new_backup_state == nbak_state_normal || missed_cycle) {
|
|
if (diff_file)
|
|
diff_pending_close = true;
|
|
// Page allocation table cache is no longer valid.
|
|
if (alloc_table) {
|
|
NBAK_TRACE(("Dropping alloc table"));
|
|
#ifdef SUPERSERVER
|
|
adjust_state_lock->enter();
|
|
#endif
|
|
delete alloc_table;
|
|
alloc_table = NULL;
|
|
last_allocated_page = 0;
|
|
#ifdef SUPERSERVER
|
|
adjust_state_lock->leave();
|
|
#endif
|
|
}
|
|
}
|
|
#ifndef SUPERSERVER
|
|
if (diff_use_count && new_backup_state == nbak_state_normal) {
|
|
database->dbb_flags |= DBB_bugcheck;
|
|
status[0] = isc_arg_gds;
|
|
status[1] = isc_bug_check;
|
|
status[2] = isc_arg_string;
|
|
status[3] =
|
|
(ISC_STATUS)(U_IPTR) ERR_cstring("Difference file is in use while backup is already finished");
|
|
status[4] = isc_arg_end;
|
|
return false;
|
|
}
|
|
#endif
|
|
if (diff_pending_close
|
|
#ifndef SUPERSERVER
|
|
&& diff_use_count == 0
|
|
#endif
|
|
)
|
|
{
|
|
NBAK_TRACE(("Close difference file"));
|
|
#ifdef SUPERSERVER
|
|
adjust_state_lock->enter();
|
|
#endif
|
|
#ifndef SUPERSERVER
|
|
diff_generation++;
|
|
#endif
|
|
PIO_close(diff_file);
|
|
diff_file = NULL;
|
|
#ifdef SUPERSERVER
|
|
adjust_state_lock->leave();
|
|
#endif
|
|
diff_pending_close = false;
|
|
}
|
|
if (new_backup_state != nbak_state_normal && !diff_file) {
|
|
#ifdef SUPERSERVER
|
|
adjust_state_lock->enter();
|
|
#endif
|
|
try {
|
|
NBAK_TRACE(("Open difference file"));
|
|
diff_file = PIO_open(database, diff_name, strlen(diff_name), false,
|
|
NULL, diff_name, strlen(diff_name));
|
|
} catch(const std::exception& ex) {
|
|
#ifdef SUPERSERVER
|
|
adjust_state_lock->leave();
|
|
#endif
|
|
Firebird::stuff_exception(status, ex);
|
|
gds__log_status(diff_name, status);
|
|
return false;
|
|
}
|
|
#ifdef SUPERSERVER
|
|
adjust_state_lock->leave();
|
|
#endif
|
|
}
|
|
// Adjust state at the very and to ensure proper error handling
|
|
backup_state = new_backup_state;
|
|
return true;
|
|
}
|
|
|