mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-27 06:43:04 +01:00
2441 lines
64 KiB
C++
2441 lines
64 KiB
C++
/*
|
|
* PROGRAM: JRD Access Method
|
|
* MODULE: pag.cpp
|
|
* DESCRIPTION: Page level ods manager
|
|
*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.Inprise.com/IPL.html
|
|
*
|
|
* Software distributed under the License is distributed on an
|
|
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*
|
|
* Modified by: Patrick J. P. Griffin
|
|
* Date: 11/29/2000
|
|
* Problem: Bug 116733 Too many generators corrupt database.
|
|
* DPM_gen_id was not calculating page and offset correctly.
|
|
* Change: Caculate pgc_gpg, number of generators per page,
|
|
* for use in DPM_gen_id.
|
|
*
|
|
* 2001.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
|
|
* conditionals, as the engine now fully supports
|
|
* readonly databases.
|
|
*
|
|
* 2001.08.07 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
|
|
* conditionals, second attempt
|
|
*
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "MAC" and "MAC_CP" defines
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "Apollo" port
|
|
|
|
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete ports:
|
|
* - EPSON, DELTA, IMP, NCR3000, M88K
|
|
* - HP9000 s300 and Apollo
|
|
*
|
|
* 2002.10.27 Sean Leyne - Completed removal of obsolete "DG_X86" port
|
|
* 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "UNIXWARE" port
|
|
* 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "Ultrix" port
|
|
* 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "Ultrix/MIPS" port
|
|
*
|
|
* 2002.10.28 Sean Leyne - Completed removal of obsolete "DGUX" port
|
|
* 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "MPEXL" port
|
|
* 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "DecOSF" port
|
|
* 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "SGI" port
|
|
*
|
|
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
|
|
*
|
|
*/
|
|
|
|
|
|
#include "firebird.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#ifdef WIN_NT
|
|
#include <process.h>
|
|
#endif
|
|
|
|
#include "../common/config/config.h"
|
|
#include "../common/utils_proto.h"
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/pag.h"
|
|
#include "../jrd/ods.h"
|
|
#include "../jrd/os/pio.h"
|
|
#include "../common/os/path_utils.h"
|
|
#include "../common/gdsassert.h"
|
|
#include "../jrd/lck.h"
|
|
#include "../jrd/sdw.h"
|
|
#include "../jrd/cch.h"
|
|
#include "../jrd/nbak.h"
|
|
#include "../jrd/tra.h"
|
|
#include "../jrd/vio_debug.h"
|
|
#include "../jrd/cch_proto.h"
|
|
#include "../jrd/dpm_proto.h"
|
|
#include "../jrd/err_proto.h"
|
|
#include "../yvalve/gds_proto.h"
|
|
#include "../jrd/lck_proto.h"
|
|
#include "../jrd/met_proto.h"
|
|
#include "../jrd/mov_proto.h"
|
|
#include "../jrd/ods_proto.h"
|
|
#include "../jrd/pag_proto.h"
|
|
#include "../jrd/os/pio_proto.h"
|
|
#include "../common/isc_f_proto.h"
|
|
#include "../jrd/TempSpace.h"
|
|
#include "../jrd/extds/ExtDS.h"
|
|
#include "../common/classes/DbImplementation.h"
|
|
#include "../jrd/CryptoManager.h"
|
|
|
|
namespace Ods
|
|
{
|
|
enum ClumpOper { CLUMP_ADD, CLUMP_REPLACE, CLUMP_REPLACE_ONLY };
|
|
}
|
|
|
|
using namespace Jrd;
|
|
using namespace Ods;
|
|
using namespace Firebird;
|
|
|
|
static void add_clump(thread_db* tdbb, USHORT type, USHORT len, const UCHAR* entry, ClumpOper mode);
|
|
static ULONG ensureDiskSpace(thread_db* tdbb, WIN* pip_window, const PageNumber pageNum);
|
|
static void find_clump_space(thread_db* tdbb, WIN*, pag**, USHORT, USHORT, const UCHAR*);
|
|
static bool find_type(thread_db* tdbb, WIN*, pag**, USHORT, USHORT, UCHAR**, const UCHAR**);
|
|
|
|
inline void err_post_if_database_is_readonly(const Database* dbb)
|
|
{
|
|
if (dbb->readOnly())
|
|
ERR_post(Arg::Gds(isc_read_only_database));
|
|
}
|
|
|
|
static const char* const SCRATCH = "fb_table_";
|
|
|
|
static const int MIN_EXTEND_BYTES = 128 * 1024; // 128KB
|
|
|
|
// CVC: Since nobody checks the result from this function (strange!), I changed
|
|
// bool to void as the return type but left the result returned as comment.
|
|
static void add_clump(thread_db* tdbb, USHORT type, USHORT len, const UCHAR* entry, ClumpOper mode)
|
|
{
|
|
/***********************************************
|
|
*
|
|
* a d d _ c l u m p
|
|
*
|
|
***********************************************
|
|
*
|
|
* Functional description
|
|
* Adds a clump to log/header page.
|
|
* mode
|
|
* 0 - add CLUMP_ADD
|
|
* 1 - replace CLUMP_REPLACE
|
|
* 2 - replace only! CLUMP_REPLACE_ONLY
|
|
* returns
|
|
* true - modified page
|
|
* false - nothing done => nobody checks this function's result.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
WIN window(DB_PAGE_SPACE, HEADER_PAGE);
|
|
pag* page = CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
header_page* header = (header_page*) page;
|
|
USHORT* end_addr = &header->hdr_end;
|
|
|
|
UCHAR* entry_p;
|
|
const UCHAR* clump_end;
|
|
if (mode != CLUMP_ADD)
|
|
{
|
|
const bool found = find_type(tdbb, &window, &page, LCK_write, type, &entry_p, &clump_end);
|
|
|
|
// If we did'nt find it and it is REPLACE_ONLY, return
|
|
|
|
if (!found && mode == CLUMP_REPLACE_ONLY)
|
|
{
|
|
CCH_RELEASE(tdbb, &window);
|
|
return; //false;
|
|
}
|
|
|
|
// If not found, just go and add the entry
|
|
|
|
if (found)
|
|
{
|
|
|
|
// if same size, overwrite it
|
|
const USHORT oldLen = entry_p[1] + 2u;
|
|
|
|
if (oldLen - 2u == len)
|
|
{
|
|
entry_p += 2;
|
|
if (len)
|
|
{
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
memcpy(entry_p, entry, len);
|
|
}
|
|
CCH_RELEASE(tdbb, &window);
|
|
return; // true;
|
|
}
|
|
|
|
// delete the entry
|
|
|
|
// Page is marked must write because of precedence problems. Later
|
|
// on we may allocate a new page and set up a precedence relationship.
|
|
// This may be the lower precedence page and so it cannot be dirty
|
|
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
|
|
*end_addr -= oldLen;
|
|
|
|
const UCHAR* r = entry_p + oldLen;
|
|
USHORT shift = clump_end - r + 1;
|
|
if (shift)
|
|
memmove(entry_p, r, shift);
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
// refetch the page
|
|
|
|
window.win_page = HEADER_PAGE;
|
|
page = CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
header = (header_page*) page;
|
|
end_addr = &header->hdr_end;
|
|
}
|
|
}
|
|
|
|
// Add the entry
|
|
|
|
find_clump_space(tdbb, &window, &page, type, len, entry);
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
return; // true;
|
|
}
|
|
|
|
|
|
USHORT PAG_add_file(thread_db* tdbb, const TEXT* file_name, SLONG start)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ a d d _ f i l e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Add a file to the current database. Return the sequence number for the new file.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
// Find current last file
|
|
|
|
PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
jrd_file* file = pageSpace->file;
|
|
while (file->fil_next) {
|
|
file = file->fil_next;
|
|
}
|
|
|
|
// Verify database file path against DatabaseAccess entry of firebird.conf
|
|
if (!JRD_verify_database_access(file_name))
|
|
{
|
|
string fileName(file_name);
|
|
ISC_systemToUtf8(fileName);
|
|
ERR_post(Arg::Gds(isc_conf_access_denied) << Arg::Str("additional database file") <<
|
|
Arg::Str(fileName));
|
|
}
|
|
|
|
// Create the file. If the sequence number comes back zero, it didn't work, so punt
|
|
|
|
const USHORT sequence = PIO_add_file(dbb, pageSpace->file, file_name, start);
|
|
if (!sequence)
|
|
return 0;
|
|
|
|
// Create header page for new file
|
|
|
|
jrd_file* next = file->fil_next;
|
|
|
|
if (dbb->dbb_flags & (DBB_force_write | DBB_no_fs_cache))
|
|
{
|
|
PIO_force_write(next, dbb->dbb_flags & DBB_force_write, dbb->dbb_flags & DBB_no_fs_cache);
|
|
}
|
|
|
|
WIN window(DB_PAGE_SPACE, next->fil_min_page);
|
|
header_page* header = (header_page*) CCH_fake(tdbb, &window, 1);
|
|
header->hdr_header.pag_type = pag_header;
|
|
header->hdr_sequence = sequence;
|
|
header->hdr_page_size = dbb->dbb_page_size;
|
|
header->hdr_data[0] = HDR_end;
|
|
header->hdr_end = HDR_SIZE;
|
|
next->fil_sequence = sequence;
|
|
|
|
#ifdef SUPPORT_RAW_DEVICES
|
|
// The following lines (taken from PAG_format_header) are needed to identify
|
|
// this file in raw_devices_validate_database as a valid database attachment.
|
|
*(ISC_TIMESTAMP*) header->hdr_creation_date = TimeStamp::getCurrentTimeStamp().value();
|
|
// should we include milliseconds or not?
|
|
//TimeStamp::round_time(header->hdr_creation_date->timestamp_time, 0);
|
|
|
|
header->hdr_ods_version = ODS_VERSION | ODS_FIREBIRD_FLAG;
|
|
DbImplementation::current.store(header);
|
|
header->hdr_ods_minor = ODS_CURRENT;
|
|
if (dbb->dbb_flags & DBB_DB_SQL_dialect_3)
|
|
header->hdr_flags |= hdr_SQL_dialect_3;
|
|
#endif
|
|
|
|
header->hdr_header.pag_pageno = window.win_page.getPageNum();
|
|
// It's header, never encrypted
|
|
PIO_write(pageSpace->file, window.win_bdb, window.win_buffer, tdbb->tdbb_status_vector);
|
|
CCH_RELEASE(tdbb, &window);
|
|
next->fil_fudge = 1;
|
|
|
|
// Update the previous header page to point to new file
|
|
|
|
file->fil_fudge = 0;
|
|
window.win_page = file->fil_min_page;
|
|
header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
if (!file->fil_min_page)
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
else
|
|
CCH_MARK(tdbb, &window);
|
|
|
|
--start;
|
|
|
|
if (file->fil_min_page)
|
|
{
|
|
PAG_add_header_entry(tdbb, header, HDR_file, static_cast<USHORT>(strlen(file_name)),
|
|
reinterpret_cast<const UCHAR*>(file_name));
|
|
PAG_add_header_entry(tdbb, header, HDR_last_page, sizeof(SLONG), (UCHAR*) &start);
|
|
}
|
|
else
|
|
{
|
|
add_clump(tdbb, HDR_file, static_cast<USHORT>(strlen(file_name)),
|
|
reinterpret_cast<const UCHAR*>(file_name), CLUMP_REPLACE);
|
|
add_clump(tdbb, HDR_last_page, sizeof(SLONG), (UCHAR*) &start, CLUMP_REPLACE);
|
|
}
|
|
|
|
header->hdr_header.pag_pageno = window.win_page.getPageNum();
|
|
// It's header, never encrypted
|
|
PIO_write(pageSpace->file, window.win_bdb, window.win_buffer, tdbb->tdbb_status_vector);
|
|
CCH_RELEASE(tdbb, &window);
|
|
if (file->fil_min_page)
|
|
file->fil_fudge = 1;
|
|
|
|
return sequence;
|
|
}
|
|
|
|
|
|
bool PAG_add_header_entry(thread_db* tdbb, header_page* header,
|
|
USHORT type, USHORT len, const UCHAR* entry)
|
|
{
|
|
/***********************************************
|
|
*
|
|
* P A G _ a d d _ h e a d e r _ e n t r y
|
|
*
|
|
***********************************************
|
|
*
|
|
* Functional description
|
|
* Add an entry to header page.
|
|
* This will be used mainly for the shadow header page and adding
|
|
* secondary files.
|
|
* Will not follow to hdr_next_page
|
|
* Will not journal changes made to page. => obsolete
|
|
* RETURNS
|
|
* true - modified page
|
|
* false - nothing done
|
|
* CVC: Nobody checks the result of this function!
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
UCHAR* p = header->hdr_data;
|
|
while (*p != HDR_end && *p != type)
|
|
p += 2u + p[1];
|
|
|
|
if (*p != HDR_end)
|
|
return false;
|
|
|
|
// We are at HDR_end, add the entry
|
|
|
|
const int free_space = dbb->dbb_page_size - header->hdr_end;
|
|
|
|
if (free_space > (2 + len))
|
|
{
|
|
fb_assert(type <= MAX_UCHAR);
|
|
fb_assert(len <= MAX_UCHAR);
|
|
*p++ = static_cast<UCHAR>(type);
|
|
*p++ = static_cast<UCHAR>(len);
|
|
|
|
if (len)
|
|
{
|
|
if (entry) {
|
|
memcpy(p, entry, len);
|
|
}
|
|
else {
|
|
memset(p, 0, len);
|
|
}
|
|
p += len;
|
|
}
|
|
|
|
*p = HDR_end;
|
|
|
|
header->hdr_end = p - (UCHAR*) header;
|
|
|
|
return true;
|
|
}
|
|
|
|
BUGCHECK(251);
|
|
return false; // Added to remove compiler warning
|
|
}
|
|
|
|
|
|
bool PAG_replace_entry_first(thread_db* tdbb, header_page* header,
|
|
USHORT type, USHORT len, const UCHAR* entry)
|
|
{
|
|
/***********************************************
|
|
*
|
|
* P A G _ r e p l a c e _ e n t r y _ f i r s t
|
|
*
|
|
***********************************************
|
|
*
|
|
* Functional description
|
|
* Replace an entry in the header page so it will become first entry
|
|
* This will be used mainly for the clumplets used for backup purposes
|
|
* because they are needed to be read without page lock
|
|
* Will not follow to hdr_next_page
|
|
* Will not journal changes made to page. => obsolete
|
|
* RETURNS
|
|
* true - modified page
|
|
* false - nothing done
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
UCHAR* p = header->hdr_data;
|
|
while (*p != HDR_end && *p != type) {
|
|
p += 2u + p[1];
|
|
}
|
|
|
|
// Remove item if found it somewhere
|
|
if (*p != HDR_end)
|
|
{
|
|
const USHORT shift = p[1] + 2u;
|
|
memmove(p, p + shift, header->hdr_end - (p - (UCHAR*) header) - shift + 1); // to preserve HDR_end
|
|
header->hdr_end -= shift;
|
|
}
|
|
|
|
if (!entry) {
|
|
return false; // We were asked just to remove item. We finished.
|
|
}
|
|
|
|
// Check if we got enough space
|
|
if (dbb->dbb_page_size - header->hdr_end <= len + 2) {
|
|
BUGCHECK(251);
|
|
}
|
|
|
|
// Actually add the item
|
|
fb_assert(len <= MAX_UCHAR);
|
|
memmove(header->hdr_data + len + 2, header->hdr_data, header->hdr_end - HDR_SIZE + 1);
|
|
header->hdr_data[0] = type;
|
|
header->hdr_data[1] = len;
|
|
memcpy(header->hdr_data + 2, entry, len);
|
|
header->hdr_end += len + 2;
|
|
|
|
return true;
|
|
}
|
|
|
|
PAG PAG_allocate(thread_db* tdbb, WIN* window)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ a l l o c a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate a page and fake a read with a write lock. This is
|
|
* the universal sequence when allocating pages.
|
|
*
|
|
**************************************/
|
|
return PAG_allocate_pages(tdbb, window, 1, false);
|
|
}
|
|
|
|
|
|
PAG PAG_allocate_pages(thread_db* tdbb, WIN* window, int cntAlloc, bool aligned)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ a l l o c a t e _ p a g e s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate number of consecutive pages and fake a read with a write lock for
|
|
* the first allocated page. If aligned is true, ensure first allocated page
|
|
* is at extent boundary.
|
|
* This is the universal sequence when allocating pages.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
PageManager& pageMgr = dbb->dbb_page_manager;
|
|
PageSpace* pageSpace = pageMgr.findPageSpace(window->win_page.getPageSpaceID());
|
|
fb_assert(pageSpace);
|
|
|
|
PAG new_page = NULL;
|
|
|
|
// Find an allocation page with something on it
|
|
|
|
int toAlloc = cntAlloc;
|
|
ULONG sequence = (cntAlloc >= PAGES_IN_EXTENT ? pageSpace->pipWithExtent : pageSpace->pipHighWater);
|
|
for (; toAlloc > 0; sequence++)
|
|
{
|
|
WIN pip_window(pageSpace->pageSpaceID,
|
|
(sequence == 0) ? pageSpace->pipFirst : sequence * dbb->dbb_page_manager.pagesPerPIP - 1);
|
|
|
|
page_inv_page* pip_page = (page_inv_page*) CCH_FETCH(tdbb, &pip_window, LCK_write, pag_pages);
|
|
|
|
ULONG firstBit = MAX_ULONG, lastBit = MAX_ULONG;
|
|
|
|
ULONG pipUsed = pip_page->pip_used;
|
|
ULONG pipMin = (cntAlloc >= PAGES_IN_EXTENT ? pip_page->pip_min : dbb->dbb_page_manager.pagesPerPIP);
|
|
ULONG pipExtent = MAX_ULONG;
|
|
|
|
UCHAR* bytes = 0;
|
|
const UCHAR* end = (UCHAR*) pip_page + dbb->dbb_page_size;
|
|
|
|
// Some pages (such as SCN or new PIP pages) could be allocated before requested pages.
|
|
// Remember its numbers to later clear corresponding bits from current PIP.
|
|
HalfStaticArray<ULONG, 8> extraPages;
|
|
|
|
const ULONG freeBit = (cntAlloc >= PAGES_IN_EXTENT) ? pip_page->pip_extent : pip_page->pip_min;
|
|
for (bytes = &pip_page->pip_bits[freeBit >> 3]; bytes < end; bytes++)
|
|
{
|
|
if (*bytes == 0)
|
|
{
|
|
toAlloc = cntAlloc;
|
|
continue;
|
|
}
|
|
|
|
// 'byte' is not zero, so it describes at least one free page.
|
|
UCHAR bit = 1;
|
|
for (SLONG i = 0; i < 8; i++, bit <<= 1)
|
|
{
|
|
if (!(bit & *bytes))
|
|
{
|
|
toAlloc = cntAlloc;
|
|
continue;
|
|
}
|
|
|
|
lastBit = ((bytes - pip_page->pip_bits) << 3) + i;
|
|
|
|
const ULONG pageNum = lastBit + sequence * pageMgr.pagesPerPIP;
|
|
|
|
// Check if we need new SCN page.
|
|
// SCN pages allocated at every pagesPerSCN pages in database.
|
|
const bool newSCN = (!pageSpace->isTemporary() && (pageNum % pageMgr.pagesPerSCN) == 0);
|
|
|
|
// Also check for new PIP page.
|
|
const bool newPIP = (lastBit == pageMgr.pagesPerPIP - 1);
|
|
fb_assert(!(newSCN && newPIP));
|
|
|
|
if (newSCN || newPIP)
|
|
{
|
|
window->win_page = pageNum;
|
|
|
|
if (lastBit + 1 > pipUsed)
|
|
pipUsed = ensureDiskSpace(tdbb, &pip_window, window->win_page);
|
|
|
|
pag* new_page = CCH_fake(tdbb, window, 1);
|
|
|
|
if (newSCN)
|
|
{
|
|
scns_page* new_scns_page = (scns_page*) new_page;
|
|
new_scns_page->scn_header.pag_type = pag_scns;
|
|
new_scns_page->scn_sequence = pageNum / pageMgr.pagesPerSCN;
|
|
}
|
|
|
|
if (newPIP)
|
|
{
|
|
page_inv_page* new_pip_page = (page_inv_page*) new_page;
|
|
new_pip_page->pip_header.pag_type = pag_pages;
|
|
const UCHAR* end = (UCHAR*) new_pip_page + dbb->dbb_page_size;
|
|
memset(new_pip_page->pip_bits, 0xff, end - new_pip_page->pip_bits);
|
|
}
|
|
|
|
CCH_must_write(tdbb, window);
|
|
CCH_RELEASE(tdbb, window);
|
|
|
|
extraPages.add(lastBit);
|
|
|
|
if (pipMin == lastBit)
|
|
pipMin++;
|
|
|
|
toAlloc = cntAlloc;
|
|
|
|
if (newSCN)
|
|
continue;
|
|
|
|
if (newPIP)
|
|
break; // we just allocated last bit at current PIP - start again at new PIP
|
|
}
|
|
|
|
if (pipMin > lastBit)
|
|
pipMin = lastBit;
|
|
|
|
// assume PAGES_IN_EXTENT == 8
|
|
if (i == 7 && *bytes == 0xFF && pipExtent > lastBit - 7)
|
|
pipExtent = lastBit - 7;
|
|
|
|
if (toAlloc == cntAlloc)
|
|
{
|
|
// found first page to allocate, check if it aligned at extent boundary
|
|
if (aligned && ((pageNum % PAGES_IN_EXTENT) != 0) )
|
|
continue;
|
|
|
|
firstBit = lastBit;
|
|
}
|
|
|
|
toAlloc--;
|
|
if (!toAlloc)
|
|
break;
|
|
}
|
|
|
|
if (!toAlloc)
|
|
break;
|
|
}
|
|
|
|
if (!toAlloc)
|
|
{
|
|
fb_assert(lastBit - firstBit + 1 == cntAlloc);
|
|
|
|
if (lastBit + 1 > pipUsed) {
|
|
pipUsed = ensureDiskSpace(tdbb, &pip_window,
|
|
PageNumber(pageSpace->pageSpaceID, lastBit + sequence * pageMgr.pagesPerPIP));
|
|
}
|
|
|
|
window->win_page = firstBit + sequence * pageMgr.pagesPerPIP;
|
|
new_page = CCH_fake(tdbb, window, 1);
|
|
fb_assert(new_page);
|
|
|
|
CCH_MARK(tdbb, &pip_window);
|
|
|
|
for (ULONG i = firstBit; i <= lastBit; i++)
|
|
{
|
|
UCHAR* byte = &pip_page->pip_bits[i / 8];
|
|
int mask = 1 << (i % 8);
|
|
*byte &= ~mask;
|
|
|
|
#ifdef VIO_DEBUG
|
|
VIO_trace(DEBUG_WRITES_INFO,
|
|
"\tPAG_allocate: allocated page %"SLONGFORMAT"\n",
|
|
i + sequence * pageMgr.pagesPerPIP);
|
|
#endif
|
|
}
|
|
|
|
pipMin = MIN(pipMin, firstBit);
|
|
if (pipMin == firstBit)
|
|
pipMin = lastBit + 1;
|
|
|
|
if (pipExtent == MAX_ULONG)
|
|
pipExtent = pip_page->pip_extent;
|
|
|
|
// If we found free extent on the PIP page and allocated some pages of it,
|
|
// set free extent mark after just allocated pages
|
|
// assume PAGES_IN_EXTENT == 8 (i.e. one byte of bits at PIP)
|
|
const ULONG extentByte = pipExtent / PAGES_IN_EXTENT;
|
|
if (extentByte >= firstBit / PAGES_IN_EXTENT &&
|
|
extentByte <= lastBit / PAGES_IN_EXTENT)
|
|
{
|
|
pipExtent = FB_ALIGN(lastBit + 1, PAGES_IN_EXTENT);
|
|
|
|
const ULONG firstPage = lastBit + sequence * pageMgr.pagesPerPIP;
|
|
const ULONG lastPage = pipExtent + sequence * pageMgr.pagesPerPIP;
|
|
if (firstPage / pageMgr.pagesPerSCN != lastPage / pageMgr.pagesPerSCN)
|
|
{
|
|
const ULONG scnBit = pipExtent - lastPage % pageMgr.pagesPerSCN;
|
|
if (pip_page->pip_bits[scnBit / 8] & (1 << (scnBit % 8)))
|
|
pipExtent -= PAGES_IN_EXTENT;
|
|
}
|
|
|
|
if (pipExtent == pageMgr.pagesPerPIP)
|
|
{
|
|
const UCHAR lastByte = pip_page->pip_bits[pageMgr.bytesBitPIP - 1];
|
|
if (lastByte & 0x80)
|
|
pipExtent--;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pipExtent == MAX_ULONG)
|
|
pipExtent = pageMgr.pagesPerPIP;
|
|
|
|
if (cntAlloc == 1)
|
|
pipMin = pageMgr.pagesPerPIP;
|
|
}
|
|
|
|
if (pipMin >= pageMgr.pagesPerPIP)
|
|
pageSpace->pipHighWater.compareExchange(sequence, sequence + 1);
|
|
|
|
if (pipExtent >= pageMgr.pagesPerPIP)
|
|
pageSpace->pipWithExtent.compareExchange(sequence, sequence + 1);
|
|
|
|
if (pipMin != pip_page->pip_min || pipExtent != pip_page->pip_extent ||
|
|
pipUsed != pip_page->pip_used || extraPages.getCount())
|
|
{
|
|
if (toAlloc)
|
|
CCH_MARK(tdbb, &pip_window);
|
|
|
|
pip_page->pip_min = pipMin;
|
|
pip_page->pip_extent = pipExtent;
|
|
pip_page->pip_used = pipUsed;
|
|
|
|
for (const ULONG *bit = extraPages.begin(); bit < extraPages.end(); bit++)
|
|
{
|
|
UCHAR* byte = &pip_page->pip_bits[*bit / 8];
|
|
const int mask = 1 << (*bit % 8);
|
|
*byte &= ~mask;
|
|
|
|
#ifdef VIO_DEBUG
|
|
VIO_trace(DEBUG_WRITES_INFO,
|
|
"\tPAG_allocate: allocated page %"SLONGFORMAT"\n",
|
|
bit + sequence * pageMgr.pagesPerPIP);
|
|
#endif
|
|
}
|
|
|
|
if (extraPages.getCount())
|
|
CCH_must_write(tdbb, &pip_window);
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &pip_window);
|
|
|
|
if (new_page)
|
|
CCH_precedence(tdbb, window, pip_window.win_page);
|
|
}
|
|
|
|
return new_page;
|
|
}
|
|
|
|
|
|
static ULONG ensureDiskSpace(thread_db* tdbb, WIN* pip_window, const PageNumber pageNum)
|
|
{
|
|
Database* dbb = tdbb->getDatabase();
|
|
PageManager& pageMgr = dbb->dbb_page_manager;
|
|
PageSpace* pageSpace = pageMgr.findPageSpace(pageNum.getPageSpaceID());
|
|
|
|
const page_inv_page* pip_page = (page_inv_page*) pip_window->win_buffer;
|
|
ULONG pipUsed = pip_page->pip_used;
|
|
const ULONG sequence = pageNum.getPageNum() / pageMgr.pagesPerPIP;
|
|
const ULONG relative_bit = pageNum.getPageNum() - sequence * pageMgr.pagesPerPIP;
|
|
|
|
BackupManager::StateReadGuard stateGuard(tdbb);
|
|
const bool nbak_stalled = dbb->dbb_backup_manager->getState() == Ods::hdr_nbak_stalled;
|
|
|
|
USHORT next_init_pages = 1;
|
|
// ensure there are space on disk for faked page
|
|
if (relative_bit + 1 > pip_page->pip_used)
|
|
{
|
|
fb_assert(relative_bit >= pip_page->pip_used);
|
|
|
|
USHORT init_pages = 0;
|
|
if (!nbak_stalled)
|
|
{
|
|
init_pages = 1;
|
|
if (!(dbb->dbb_flags & DBB_no_reserve))
|
|
{
|
|
const int minExtendPages = MIN_EXTEND_BYTES / dbb->dbb_page_size;
|
|
|
|
init_pages = sequence ? 64 : MIN(pip_page->pip_used / 16, 64);
|
|
|
|
// don't touch pages belongs to the next PIP
|
|
init_pages = MIN(init_pages, pageMgr.pagesPerPIP - pip_page->pip_used);
|
|
|
|
if (init_pages < minExtendPages)
|
|
init_pages = 1;
|
|
}
|
|
|
|
if (init_pages < relative_bit + 1 - pip_page->pip_used)
|
|
init_pages = relative_bit + 1 - pip_page->pip_used;
|
|
|
|
//init_pages = FB_ALIGN(init_pages, PAGES_IN_EXTENT);
|
|
|
|
next_init_pages = init_pages;
|
|
|
|
ISC_STATUS_ARRAY status;
|
|
const ULONG start = sequence * pageMgr.pagesPerPIP + pip_page->pip_used;
|
|
|
|
init_pages = PIO_init_data(dbb, pageSpace->file, status, start, init_pages);
|
|
}
|
|
|
|
if (init_pages)
|
|
{
|
|
pipUsed += init_pages;
|
|
}
|
|
else
|
|
{
|
|
// PIO_init_data returns zero - perhaps it is not supported,
|
|
// no space left on disk or IO error occurred. Try to write
|
|
// one page and handle IO errors if any.
|
|
WIN window(pageNum);
|
|
CCH_fake(tdbb, &window, 1);
|
|
CCH_must_write(tdbb, &window);
|
|
try
|
|
{
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
catch (const status_exception&)
|
|
{
|
|
// forget about this page as if we never tried to fake it
|
|
CCH_forget_page(tdbb, &window);
|
|
|
|
// normally all page buffers now released by CCH_unwind
|
|
// only exception is when TDBB_no_cache_unwind flag is set
|
|
if (tdbb->tdbb_flags & TDBB_no_cache_unwind)
|
|
CCH_RELEASE(tdbb, pip_window);
|
|
|
|
throw;
|
|
}
|
|
|
|
pipUsed = relative_bit + 1;
|
|
}
|
|
}
|
|
|
|
if (!(dbb->dbb_flags & DBB_no_reserve) && !nbak_stalled)
|
|
{
|
|
const ULONG initialized = sequence * pageMgr.pagesPerPIP + pip_page->pip_used;
|
|
|
|
// At this point we ensure database has at least "initialized" pages
|
|
// allocated. To avoid file growth by few pages when all this space
|
|
// will be used, extend file up to initialized + next_init_pages now
|
|
pageSpace->extend(tdbb, initialized + next_init_pages, false);
|
|
}
|
|
|
|
return pipUsed;
|
|
}
|
|
|
|
|
|
SLONG PAG_attachment_id(thread_db* tdbb)
|
|
{
|
|
/******************************************
|
|
*
|
|
* P A G _ a t t a c h m e n t _ i d
|
|
*
|
|
******************************************
|
|
*
|
|
* Functional description
|
|
* Get attachment id. If don't have one, get one. As a side
|
|
* effect, get a lock on it as well.
|
|
*
|
|
******************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
|
|
Jrd::Attachment* attachment = tdbb->getAttachment();
|
|
WIN window(DB_PAGE_SPACE, -1);
|
|
|
|
// If we've been here before just return the id
|
|
|
|
if (attachment->att_id_lock)
|
|
return attachment->att_attachment_id;
|
|
|
|
// Get new attachment id
|
|
|
|
if (dbb->readOnly()) {
|
|
attachment->att_attachment_id = dbb->dbb_attachment_id + dbb->generateAttachmentId(tdbb);
|
|
}
|
|
else
|
|
{
|
|
window.win_page = HEADER_PAGE_NUMBER;
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
CCH_MARK(tdbb, &window);
|
|
attachment->att_attachment_id = ++header->hdr_attachment_id;
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
attachment->initLocks(tdbb);
|
|
Monitoring::publishAttachment(tdbb);
|
|
|
|
return attachment->att_attachment_id;
|
|
}
|
|
|
|
|
|
bool PAG_delete_clump_entry(thread_db* tdbb, USHORT type)
|
|
{
|
|
/***********************************************
|
|
*
|
|
* P A G _ d e l e t e _ c l u m p _ e n t r y
|
|
*
|
|
***********************************************
|
|
*
|
|
* Functional description
|
|
* Gets rid on the entry 'type' from page.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
WIN window(DB_PAGE_SPACE, HEADER_PAGE);
|
|
|
|
pag* page = CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
|
|
UCHAR* entry_p;
|
|
const UCHAR* clump_end;
|
|
if (!find_type(tdbb, &window, &page, LCK_write, type, &entry_p, &clump_end))
|
|
{
|
|
CCH_RELEASE(tdbb, &window);
|
|
return false;
|
|
}
|
|
CCH_MARK(tdbb, &window);
|
|
|
|
header_page* header = (header_page*) page;
|
|
USHORT* end_addr = &header->hdr_end;
|
|
|
|
*end_addr -= (2u + entry_p[1]);
|
|
|
|
const UCHAR* r = entry_p + 2 + entry_p[1];
|
|
USHORT shift = clump_end - r + 1;
|
|
if (shift)
|
|
memmove(entry_p, r, shift);
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PAG_format_header(thread_db* tdbb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ f o r m a t _ h e a d e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Create the header page for a new file.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
// Initialize header page
|
|
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
header_page* header = (header_page*) CCH_fake(tdbb, &window, 1);
|
|
header->hdr_header.pag_scn = 0;
|
|
*(ISC_TIMESTAMP*) header->hdr_creation_date = TimeStamp::getCurrentTimeStamp().value();
|
|
// should we include milliseconds or not?
|
|
//TimeStamp::round_time(header->hdr_creation_date->timestamp_time, 0);
|
|
header->hdr_header.pag_type = pag_header;
|
|
header->hdr_page_size = dbb->dbb_page_size;
|
|
header->hdr_ods_version = ODS_VERSION | ODS_FIREBIRD_FLAG;
|
|
DbImplementation::current.store(header);
|
|
header->hdr_ods_minor = ODS_CURRENT;
|
|
header->hdr_oldest_transaction = 1;
|
|
header->hdr_end = HDR_SIZE;
|
|
header->hdr_data[0] = HDR_end;
|
|
|
|
if (dbb->dbb_flags & DBB_DB_SQL_dialect_3) {
|
|
header->hdr_flags |= hdr_SQL_dialect_3;
|
|
}
|
|
|
|
dbb->dbb_ods_version = header->hdr_ods_version & ~ODS_FIREBIRD_FLAG;
|
|
dbb->dbb_minor_version = header->hdr_ods_minor;
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
|
|
void PAG_format_pip(thread_db* tdbb, PageSpace& pageSpace)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ f o r m a t _ p i p
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Create a page inventory page to
|
|
* complete the formatting of a new file
|
|
* into a rudimentary database.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
// Initialize first SCN's Page
|
|
pageSpace.scnFirst = 0;
|
|
if (!pageSpace.isTemporary())
|
|
{
|
|
pageSpace.scnFirst = FIRST_SCN_PAGE;
|
|
|
|
WIN window(pageSpace.pageSpaceID, pageSpace.scnFirst);
|
|
scns_page* page = (scns_page*) CCH_fake(tdbb, &window, 1);
|
|
|
|
page->scn_header.pag_type = pag_scns;
|
|
page->scn_sequence = 0;
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
// Initialize Page Inventory Page
|
|
{
|
|
pageSpace.pipFirst = FIRST_PIP_PAGE;
|
|
|
|
WIN window(pageSpace.pageSpaceID, pageSpace.pipFirst);
|
|
page_inv_page* pages = (page_inv_page*) CCH_fake(tdbb, &window, 1);
|
|
|
|
pages->pip_header.pag_type = pag_pages;
|
|
pages->pip_used = (pageSpace.scnFirst ? pageSpace.scnFirst : pageSpace.pipFirst) + 1;
|
|
pages->pip_min = pages->pip_used;
|
|
int count = dbb->dbb_page_size - static_cast<int>(offsetof(page_inv_page, pip_bits[0]));
|
|
|
|
memset(pages->pip_bits, 0xFF, count);
|
|
|
|
pages->pip_bits[0] &= ~(1 | 2);
|
|
if (pageSpace.scnFirst)
|
|
pages->pip_bits[0] &= ~(1 << pageSpace.scnFirst);
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef NOT_USED_OR_REPLACED
|
|
bool PAG_get_clump(thread_db* tdbb, SLONG page_num, USHORT type, USHORT* inout_len, UCHAR* entry)
|
|
{
|
|
/***********************************************
|
|
*
|
|
* P A G _ g e t _ c l u m p
|
|
*
|
|
***********************************************
|
|
*
|
|
* Functional description
|
|
* Find 'type' clump in page_num
|
|
* true - Found it
|
|
* false - Not present
|
|
* RETURNS
|
|
* value of clump in entry
|
|
* length in inout_len <-> input and output value to avoid B.O.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
WIN window(DB_PAGE_SPACE, page_num);
|
|
|
|
if (page_num != HEADER_PAGE)
|
|
ERR_post(Arg::Gds(isc_page_type_err));
|
|
|
|
pag* page = CCH_FETCH(tdbb, &window, LCK_read, pag_header);
|
|
|
|
UCHAR* entry_p;
|
|
const UCHAR* dummy;
|
|
if (!find_type(tdbb, &window, &page, LCK_read, type, &entry_p, &dummy))
|
|
{
|
|
CCH_RELEASE(tdbb, &window);
|
|
*inout_len = 0;
|
|
return false;
|
|
}
|
|
|
|
USHORT old_len = *inout_len;
|
|
*inout_len = entry_p[1];
|
|
entry_p += 2;
|
|
|
|
if (*inout_len)
|
|
{
|
|
// Avoid the B.O. but inform the caller the buffer is bigger.
|
|
if (*inout_len < old_len)
|
|
old_len = *inout_len;
|
|
memcpy(entry, entry_p, old_len);
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
void PAG_header(thread_db* tdbb, bool info)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ h e a d e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Checkout database header page.
|
|
* Done through the page cache.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
|
|
Jrd::Attachment* attachment = tdbb->getAttachment();
|
|
fb_assert(attachment);
|
|
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_header);
|
|
|
|
try {
|
|
|
|
if (header->hdr_next_transaction)
|
|
{
|
|
if (header->hdr_oldest_active > header->hdr_next_transaction)
|
|
BUGCHECK(266); // next transaction older than oldest active
|
|
|
|
if (header->hdr_oldest_transaction > header->hdr_next_transaction)
|
|
BUGCHECK(267); // next transaction older than oldest transaction
|
|
}
|
|
|
|
if (header->hdr_flags & hdr_SQL_dialect_3)
|
|
dbb->dbb_flags |= DBB_DB_SQL_dialect_3;
|
|
|
|
jrd_rel* relation = MET_relation(tdbb, 0);
|
|
RelationPages* relPages = relation->getBasePages();
|
|
if (!relPages->rel_pages)
|
|
{
|
|
// 21-Dec-2003 Nickolay Samofatov
|
|
// No need to re-set first page for RDB$PAGES relation since
|
|
// current code cannot change its location after database creation.
|
|
// Currently, this change only affects isc_database_info call,
|
|
// the only call which may call PAG_header multiple times.
|
|
// In fact, this isc_database_info behavior seems dangerous to me,
|
|
// but let somebody else fix that problem, I just fix the memory leak.
|
|
vcl* vector = vcl::newVector(*relation->rel_pool, 1);
|
|
relPages->rel_pages = vector;
|
|
(*vector)[0] = header->hdr_PAGES;
|
|
}
|
|
|
|
dbb->dbb_next_transaction = header->hdr_next_transaction;
|
|
|
|
if (!info || dbb->dbb_oldest_transaction < header->hdr_oldest_transaction) {
|
|
dbb->dbb_oldest_transaction = header->hdr_oldest_transaction;
|
|
}
|
|
if (!info || dbb->dbb_oldest_active < header->hdr_oldest_active) {
|
|
dbb->dbb_oldest_active = header->hdr_oldest_active;
|
|
}
|
|
if (!info || dbb->dbb_oldest_snapshot < header->hdr_oldest_snapshot) {
|
|
dbb->dbb_oldest_snapshot = header->hdr_oldest_snapshot;
|
|
}
|
|
|
|
dbb->dbb_attachment_id = header->hdr_attachment_id;
|
|
dbb->dbb_creation_date = *(ISC_TIMESTAMP*) header->hdr_creation_date;
|
|
|
|
if (header->hdr_flags & hdr_read_only)
|
|
{
|
|
// If Header Page flag says the database is ReadOnly, gladly accept it.
|
|
dbb->dbb_flags &= ~DBB_being_opened_read_only;
|
|
dbb->dbb_flags |= DBB_read_only;
|
|
}
|
|
|
|
// If hdr_read_only is not set...
|
|
if (!(header->hdr_flags & hdr_read_only) && (dbb->dbb_flags & DBB_being_opened_read_only))
|
|
{
|
|
// Looks like the Header page says, it is NOT ReadOnly!! But the database
|
|
// file system permission gives only ReadOnly access. Punt out with
|
|
// isc_no_priv error (no privileges)
|
|
ERR_post(Arg::Gds(isc_no_priv) << Arg::Str("read-write") <<
|
|
Arg::Str("database") <<
|
|
Arg::Str(attachment->att_filename));
|
|
}
|
|
|
|
const bool useFSCache = dbb->dbb_bcb->bcb_count <
|
|
ULONG(dbb->dbb_config->getFileSystemCacheThreshold());
|
|
|
|
if ((header->hdr_flags & hdr_force_write) || !useFSCache)
|
|
{
|
|
dbb->dbb_flags |=
|
|
(header->hdr_flags & hdr_force_write ? DBB_force_write : 0) |
|
|
(useFSCache ? 0 : DBB_no_fs_cache);
|
|
|
|
const bool forceWrite = dbb->dbb_flags & DBB_force_write;
|
|
const bool notUseFSCache = dbb->dbb_flags & DBB_no_fs_cache;
|
|
|
|
PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
for (jrd_file* file = pageSpace->file; file; file = file->fil_next)
|
|
{
|
|
PIO_force_write(file,
|
|
forceWrite && !(header->hdr_flags & hdr_read_only),
|
|
notUseFSCache);
|
|
}
|
|
|
|
if (dbb->dbb_backup_manager->getState() != Ods::hdr_nbak_normal)
|
|
dbb->dbb_backup_manager->setForcedWrites(forceWrite, notUseFSCache);
|
|
}
|
|
|
|
if (header->hdr_flags & hdr_no_reserve)
|
|
dbb->dbb_flags |= DBB_no_reserve;
|
|
|
|
const USHORT sd_flags = header->hdr_flags & hdr_shutdown_mask;
|
|
if (sd_flags)
|
|
{
|
|
dbb->dbb_ast_flags |= DBB_shutdown;
|
|
if (sd_flags == hdr_shutdown_full)
|
|
dbb->dbb_ast_flags |= DBB_shutdown_full;
|
|
else if (sd_flags == hdr_shutdown_single)
|
|
dbb->dbb_ast_flags |= DBB_shutdown_single;
|
|
}
|
|
|
|
} // try
|
|
catch (const Exception&)
|
|
{
|
|
CCH_RELEASE(tdbb, &window);
|
|
throw;
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
|
|
void PAG_header_init(thread_db* tdbb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ h e a d e r _ i n i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Checkout the core part of the database header page.
|
|
* It includes the fields required to setup the I/O layer:
|
|
* ODS version, page size, page buffers.
|
|
* Done using a physical page read.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
|
|
Jrd::Attachment* const attachment = tdbb->getAttachment();
|
|
fb_assert(attachment);
|
|
|
|
// Allocate a spare buffer which is large enough,
|
|
// and set up to release it in case of error; note
|
|
// that dbb_page_size has not been set yet, so we
|
|
// can't depend on this.
|
|
//
|
|
// Make sure that buffer is aligned on a page boundary
|
|
// and unit of transfer is a multiple of physical disk
|
|
// sector for raw disk access.
|
|
|
|
SCHAR temp_buffer[2 * MIN_PAGE_SIZE];
|
|
SCHAR* const temp_page = FB_ALIGN(temp_buffer, MIN_PAGE_SIZE);
|
|
|
|
PIO_header(dbb, temp_page, MIN_PAGE_SIZE);
|
|
const header_page* header = (header_page*) temp_page;
|
|
|
|
if (header->hdr_header.pag_type != pag_header || header->hdr_sequence) {
|
|
ERR_post(Arg::Gds(isc_bad_db_format) << Arg::Str(attachment->att_filename));
|
|
}
|
|
|
|
const USHORT ods_version = header->hdr_ods_version & ~ODS_FIREBIRD_FLAG;
|
|
|
|
if (!Ods::isSupported(header->hdr_ods_version, header->hdr_ods_minor))
|
|
{
|
|
ERR_post(Arg::Gds(isc_wrong_ods) << Arg::Str(attachment->att_filename) <<
|
|
Arg::Num(ods_version) <<
|
|
Arg::Num(header->hdr_ods_minor) <<
|
|
Arg::Num(ODS_VERSION) <<
|
|
Arg::Num(ODS_CURRENT));
|
|
}
|
|
|
|
// Note that if this check is turned on, it should be recoded in order that
|
|
// the Intel platforms can share databases. At present (Feb 95) it is possible
|
|
// to share databases between Windows and NT, but not with NetWare. Sharing
|
|
// databases with OS/2 is unknown and needs to be investigated. The CLASS was
|
|
// initially 8 for all Intel platforms, but was changed after 4.0 was released
|
|
// in order to allow differentiation between databases created on various
|
|
// platforms. This should allow us in future to identify where databases were
|
|
// created. Even when we get to the stage where databases created on PC platforms
|
|
// are sharable between all platforms, it would be useful to identify where they
|
|
// were created for debugging purposes. - Deej 2/6/95
|
|
//
|
|
// Re-enable and recode the check to avoid BUGCHECK messages when database
|
|
// is accessed with engine built for another architecture. - Nickolay 9-Feb-2005
|
|
|
|
if (!DbImplementation(header).compatible(DbImplementation::current))
|
|
{
|
|
ERR_post(Arg::Gds(isc_bad_db_format) << Arg::Str(attachment->att_filename));
|
|
}
|
|
|
|
if (header->hdr_page_size < MIN_NEW_PAGE_SIZE || header->hdr_page_size > MAX_PAGE_SIZE)
|
|
{
|
|
ERR_post(Arg::Gds(isc_bad_db_format) << Arg::Str(attachment->att_filename));
|
|
}
|
|
|
|
dbb->dbb_ods_version = ods_version;
|
|
dbb->dbb_minor_version = header->hdr_ods_minor;
|
|
|
|
dbb->dbb_page_size = header->hdr_page_size;
|
|
dbb->dbb_page_buffers = header->hdr_page_buffers;
|
|
}
|
|
|
|
|
|
void PAG_init(thread_db* tdbb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ i n i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Initialize stuff for page handling.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
PageManager& pageMgr = dbb->dbb_page_manager;
|
|
PageSpace* pageSpace = pageMgr.findPageSpace(DB_PAGE_SPACE);
|
|
fb_assert(pageSpace);
|
|
|
|
pageMgr.bytesBitPIP = Ods::bytesBitPIP(dbb->dbb_page_size);
|
|
pageMgr.pagesPerPIP = Ods::pagesPerPIP(dbb->dbb_page_size);
|
|
pageMgr.pagesPerSCN = Ods::pagesPerSCN(dbb->dbb_page_size);
|
|
pageSpace->pipFirst = FIRST_PIP_PAGE;
|
|
pageSpace->scnFirst = FIRST_SCN_PAGE;
|
|
|
|
pageMgr.transPerTIP = Ods::transPerTIP(dbb->dbb_page_size);
|
|
|
|
// dbb_ods_version can be 0 when a new database is being created
|
|
fb_assert((dbb->dbb_ods_version == 0) || (dbb->dbb_ods_version >= ODS_VERSION12));
|
|
pageMgr.gensPerPage = Ods::gensPerPage(dbb->dbb_page_size);
|
|
|
|
dbb->dbb_dp_per_pp = Ods::dataPagesPerPP(dbb->dbb_page_size);
|
|
dbb->dbb_max_records = Ods::maxRecsPerDP(dbb->dbb_page_size);
|
|
dbb->dbb_max_idx = Ods::maxIndices(dbb->dbb_page_size);
|
|
|
|
// Compute prefetch constants from database page size and maximum prefetch
|
|
// transfer size. Double pages per prefetch request so that cache reader
|
|
// can overlap prefetch I/O with database computation over previously prefetched pages.
|
|
#ifdef SUPERSERVER_V2
|
|
dbb->dbb_prefetch_sequence = PREFETCH_MAX_TRANSFER / dbb->dbb_page_size;
|
|
dbb->dbb_prefetch_pages = dbb->dbb_prefetch_sequence * 2;
|
|
#endif
|
|
}
|
|
|
|
|
|
void PAG_init2(thread_db* tdbb, USHORT shadow_number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ i n i t 2
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform second phase of page initialization -- the eternal
|
|
* search for additional files.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
ISC_STATUS* status = tdbb->tdbb_status_vector;
|
|
|
|
// allocate a spare buffer which is large enough,
|
|
// and set up to release it in case of error. Align
|
|
// the temporary page buffer for raw disk access.
|
|
|
|
Array<SCHAR> temp;
|
|
SCHAR* const temp_page =
|
|
FB_ALIGN(temp.getBuffer(dbb->dbb_page_size + MIN_PAGE_SIZE), MIN_PAGE_SIZE);
|
|
|
|
PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
jrd_file* file = pageSpace->file;
|
|
if (shadow_number)
|
|
{
|
|
Shadow* shadow = dbb->dbb_shadow;
|
|
for (; shadow; shadow = shadow->sdw_next)
|
|
{
|
|
if (shadow->sdw_number == shadow_number)
|
|
{
|
|
file = shadow->sdw_file;
|
|
break;
|
|
}
|
|
}
|
|
if (!shadow)
|
|
BUGCHECK(161); // msg 161 shadow block not found
|
|
}
|
|
|
|
USHORT sequence = 1;
|
|
WIN window(DB_PAGE_SPACE, -1);
|
|
|
|
TEXT buf[MAXPATHLEN + 1];
|
|
|
|
// Loop thru files and header pages until everything is open
|
|
|
|
for (;;)
|
|
{
|
|
TEXT* file_name = NULL;
|
|
window.win_page = file->fil_min_page;
|
|
USHORT file_length = 0;
|
|
ULONG last_page = 0;
|
|
BufferDesc temp_bdb(dbb->dbb_bcb);
|
|
SLONG next_page = 0;
|
|
do {
|
|
// note that we do not have to get a read lock on
|
|
// the header page (except for header page 0) because
|
|
// the only time it will be modified is when adding a file,
|
|
// which must be done with an exclusive lock on the database --
|
|
// if this changes, this policy will have to be reevaluated;
|
|
// at any rate there is a problem with getting a read lock
|
|
// because the corresponding page in the main database file may not exist
|
|
|
|
if (!file->fil_min_page)
|
|
CCH_FETCH(tdbb, &window, LCK_read, pag_header);
|
|
|
|
header_page* header = (header_page*) temp_page;
|
|
temp_bdb.bdb_buffer = (pag*) header;
|
|
temp_bdb.bdb_page = window.win_page;
|
|
|
|
// Read the required page into the local buffer
|
|
// It's header, never encrypted
|
|
PIO_read(file, &temp_bdb, (PAG) header, status);
|
|
|
|
if (shadow_number && !file->fil_min_page)
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
for (const UCHAR* p = header->hdr_data; *p != HDR_end; p += 2u + p[1])
|
|
{
|
|
switch (*p)
|
|
{
|
|
case HDR_file:
|
|
file_length = p[1];
|
|
file_name = buf;
|
|
memcpy(buf, p + 2, file_length);
|
|
break;
|
|
|
|
case HDR_last_page:
|
|
memcpy(&last_page, p + 2, sizeof(last_page));
|
|
break;
|
|
|
|
case HDR_sweep_interval:
|
|
// CVC: Let's copy it always.
|
|
//if (!dbb->readOnly())
|
|
memcpy(&dbb->dbb_sweep_interval, p + 2, sizeof(SLONG));
|
|
break;
|
|
}
|
|
}
|
|
|
|
next_page = header->hdr_next_page;
|
|
|
|
if (!shadow_number && !file->fil_min_page)
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
window.win_page = next_page;
|
|
|
|
// Make sure the header page and all the overflow header
|
|
// pages are traversed. For V4.0, only the header page for
|
|
// the primary database page will have overflow pages.
|
|
|
|
} while (next_page);
|
|
|
|
if (file->fil_min_page)
|
|
file->fil_fudge = 1;
|
|
if (!file_name)
|
|
break;
|
|
|
|
// Verify database file path against DatabaseAccess entry of firebird.conf
|
|
file_name[file_length] = 0;
|
|
if (!JRD_verify_database_access(file_name))
|
|
{
|
|
string fileName(file_name);
|
|
ISC_systemToUtf8(fileName);
|
|
ERR_post(Arg::Gds(isc_conf_access_denied) << Arg::Str("additional database file") <<
|
|
Arg::Str(fileName));
|
|
}
|
|
|
|
file->fil_next = PIO_open(dbb, file_name, file_name);
|
|
file->fil_max_page = last_page;
|
|
file = file->fil_next;
|
|
if (dbb->dbb_flags & (DBB_force_write | DBB_no_fs_cache))
|
|
{
|
|
PIO_force_write(file, dbb->dbb_flags & DBB_force_write, dbb->dbb_flags & DBB_no_fs_cache);
|
|
}
|
|
file->fil_min_page = last_page + 1;
|
|
file->fil_sequence = sequence++;
|
|
}
|
|
}
|
|
|
|
|
|
SLONG PAG_last_page(thread_db* tdbb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ l a s t _ p a g e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Compute the highest page allocated. This is called by the
|
|
* shadow stuff to dump a database.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
PageManager& pageMgr = dbb->dbb_page_manager;
|
|
PageSpace* pageSpace = pageMgr.findPageSpace(DB_PAGE_SPACE);
|
|
fb_assert(pageSpace);
|
|
|
|
const ULONG pages_per_pip = pageMgr.pagesPerPIP;
|
|
WIN window(DB_PAGE_SPACE, -1);
|
|
|
|
// Find the last page allocated
|
|
|
|
ULONG relative_bit = 0;
|
|
USHORT sequence;
|
|
for (sequence = 0; true; ++sequence)
|
|
{
|
|
window.win_page = (!sequence) ? pageSpace->pipFirst : sequence * pages_per_pip - 1;
|
|
const page_inv_page* page = (page_inv_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_pages);
|
|
const UCHAR* bits = page->pip_bits + (pages_per_pip >> 3) - 1;
|
|
while (*bits == (UCHAR) - 1)
|
|
--bits;
|
|
SSHORT bit;
|
|
for (bit = 7; bit >= 0; --bit)
|
|
{
|
|
if (!(*bits & (1 << bit)))
|
|
break;
|
|
}
|
|
relative_bit = (bits - page->pip_bits) * 8 + bit;
|
|
CCH_RELEASE(tdbb, &window);
|
|
if (relative_bit != pages_per_pip - 1)
|
|
break;
|
|
}
|
|
|
|
return sequence * pages_per_pip + relative_bit;
|
|
}
|
|
|
|
|
|
void PAG_release_page(thread_db* tdbb, const PageNumber& number, const PageNumber& prior_page)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ r e l e a s e _ p a g e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Release a page to the free page page.
|
|
*
|
|
**************************************/
|
|
|
|
fb_assert(number.getPageSpaceID() == prior_page.getPageSpaceID() ||
|
|
prior_page == ZERO_PAGE_NUMBER);
|
|
|
|
const ULONG pgNum = number.getPageNum();
|
|
PAG_release_pages(tdbb, number.getPageSpaceID(), 1, &pgNum, prior_page.getPageNum());
|
|
}
|
|
|
|
|
|
void PAG_release_pages(thread_db* tdbb, USHORT pageSpaceID, int cntRelease,
|
|
const ULONG* pgNums, const ULONG prior_page)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ r e l e a s e _ p a g e s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Release a few pages to the free page page.
|
|
*
|
|
**************************************/
|
|
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
PageManager& pageMgr = dbb->dbb_page_manager;
|
|
PageSpace* pageSpace = pageMgr.findPageSpace(pageSpaceID);
|
|
fb_assert(pageSpace);
|
|
|
|
WIN pip_window(pageSpaceID, -1);
|
|
page_inv_page* pages = NULL;
|
|
ULONG sequence = 0;
|
|
|
|
for (int i = 0; i < cntRelease; i++)
|
|
{
|
|
#ifdef VIO_DEBUG
|
|
VIO_trace(DEBUG_WRITES_INFO,
|
|
"\tPAG_release_pages: about to release page %"SLONGFORMAT"\n", pgNums[i]);
|
|
#endif
|
|
|
|
const ULONG seq = pgNums[i] / pageMgr.pagesPerPIP;
|
|
|
|
if (!pages || seq != sequence)
|
|
{
|
|
if (pages)
|
|
{
|
|
pageSpace->pipHighWater.exchangeLower(sequence);
|
|
if (pages->pip_extent < pageMgr.pagesPerPIP)
|
|
pageSpace->pipWithExtent.exchangeLower(sequence);
|
|
|
|
CCH_RELEASE(tdbb, &pip_window);
|
|
}
|
|
|
|
sequence = seq;
|
|
pip_window.win_page = (sequence == 0) ?
|
|
pageSpace->pipFirst : sequence * pageMgr.pagesPerPIP - 1;
|
|
|
|
pages = (page_inv_page*) CCH_FETCH(tdbb, &pip_window, LCK_write, pag_pages);
|
|
CCH_precedence(tdbb, &pip_window, prior_page);
|
|
CCH_MARK(tdbb, &pip_window);
|
|
}
|
|
|
|
const ULONG relative_bit = pgNums[i] % pageMgr.pagesPerPIP;
|
|
UCHAR* byte = &pages->pip_bits[relative_bit >> 3];
|
|
*byte |= 1 << (relative_bit & 7);
|
|
if (*byte == 0xFF) // assume PAGES_IN_EXTENT == 8
|
|
{
|
|
pages->pip_extent = MIN(pages->pip_extent, relative_bit & ~0x07);
|
|
}
|
|
pages->pip_min = MIN(pages->pip_min, relative_bit);
|
|
}
|
|
|
|
pageSpace->pipHighWater.exchangeLower(sequence);
|
|
|
|
if (pages->pip_extent < pageMgr.pagesPerPIP)
|
|
pageSpace->pipWithExtent.exchangeLower(sequence);
|
|
|
|
CCH_RELEASE(tdbb, &pip_window);
|
|
}
|
|
|
|
|
|
void PAG_set_force_write(thread_db* tdbb, bool flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ s e t _ f o r c e _ w r i t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Turn on/off force write.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
|
|
if (flag)
|
|
{
|
|
header->hdr_flags |= hdr_force_write;
|
|
dbb->dbb_flags |= DBB_force_write;
|
|
}
|
|
else
|
|
{
|
|
header->hdr_flags &= ~hdr_force_write;
|
|
dbb->dbb_flags &= ~DBB_force_write;
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
for (jrd_file* file = pageSpace->file; file; file = file->fil_next) {
|
|
PIO_force_write(file, flag, dbb->dbb_flags & DBB_no_fs_cache);
|
|
}
|
|
|
|
for (Shadow* shadow = dbb->dbb_shadow; shadow; shadow = shadow->sdw_next)
|
|
{
|
|
for (jrd_file* file = shadow->sdw_file; file; file = file->fil_next) {
|
|
PIO_force_write(file, flag, dbb->dbb_flags & DBB_no_fs_cache);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void PAG_set_no_reserve(thread_db* tdbb, bool flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ s e t _ n o _ r e s e r v e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Turn on/off reserving space for versions
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
|
|
if (flag)
|
|
{
|
|
header->hdr_flags |= hdr_no_reserve;
|
|
dbb->dbb_flags |= DBB_no_reserve;
|
|
}
|
|
else
|
|
{
|
|
header->hdr_flags &= ~hdr_no_reserve;
|
|
dbb->dbb_flags &= ~DBB_no_reserve;
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
|
|
void PAG_set_db_readonly(thread_db* tdbb, bool flag)
|
|
{
|
|
/*********************************************
|
|
*
|
|
* P A G _ s e t _ d b _ r e a d o n l y
|
|
*
|
|
*********************************************
|
|
*
|
|
* Functional description
|
|
* Set database access mode to readonly OR readwrite
|
|
*
|
|
*********************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
|
|
if (!flag)
|
|
{
|
|
// If the database is transitioning from RO to RW, reset the
|
|
// in-memory Database flag which indicates that the database is RO.
|
|
// This will allow the CCH subsystem to allow pages to be MARK'ed
|
|
// for WRITE operations
|
|
header->hdr_flags &= ~hdr_read_only;
|
|
dbb->dbb_flags &= ~DBB_read_only;
|
|
}
|
|
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
|
|
if (flag)
|
|
{
|
|
header->hdr_flags |= hdr_read_only;
|
|
dbb->dbb_flags |= DBB_read_only;
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
|
|
void PAG_set_db_SQL_dialect(thread_db* tdbb, SSHORT flag)
|
|
{
|
|
/*********************************************
|
|
*
|
|
* P A G _ s e t _ d b _ S Q L _ d i a l e c t
|
|
*
|
|
*********************************************
|
|
*
|
|
* Functional description
|
|
* Set database SQL dialect to SQL_DIALECT_V5 or SQL_DIALECT_V6
|
|
*
|
|
*********************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
|
|
if (flag)
|
|
{
|
|
switch (flag)
|
|
{
|
|
case SQL_DIALECT_V5:
|
|
|
|
if ((dbb->dbb_flags & DBB_DB_SQL_dialect_3) || (header->hdr_flags & hdr_SQL_dialect_3))
|
|
{
|
|
// Check the returned value here!
|
|
ERR_post_warning(Arg::Warning(isc_dialect_reset_warning));
|
|
}
|
|
|
|
dbb->dbb_flags &= ~DBB_DB_SQL_dialect_3; // set to 0
|
|
header->hdr_flags &= ~hdr_SQL_dialect_3; // set to 0
|
|
break;
|
|
|
|
case SQL_DIALECT_V6:
|
|
dbb->dbb_flags |= DBB_DB_SQL_dialect_3; // set to dialect 3
|
|
header->hdr_flags |= hdr_SQL_dialect_3; // set to dialect 3
|
|
break;
|
|
|
|
default:
|
|
CCH_RELEASE(tdbb, &window);
|
|
ERR_post(Arg::Gds(isc_inv_dialect_specified) << Arg::Num(flag) <<
|
|
Arg::Gds(isc_valid_db_dialects) << Arg::Str("1 and 3") <<
|
|
Arg::Gds(isc_dialect_not_changed));
|
|
break;
|
|
}
|
|
}
|
|
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
|
|
void PAG_set_page_buffers(thread_db* tdbb, ULONG buffers)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ s e t _ p a g e _ b u f f e r s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set database-specific page buffer cache
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
err_post_if_database_is_readonly(dbb);
|
|
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
|
|
CCH_MARK_MUST_WRITE(tdbb, &window);
|
|
header->hdr_page_buffers = buffers;
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
|
|
void PAG_sweep_interval(thread_db* tdbb, SLONG interval)
|
|
{
|
|
/**************************************
|
|
*
|
|
* P A G _ s w e e p _ i n t e r v a l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set sweep interval.
|
|
*
|
|
**************************************/
|
|
|
|
SET_TDBB(tdbb);
|
|
add_clump(tdbb, HDR_sweep_interval, sizeof(SLONG), (UCHAR*) &interval, CLUMP_REPLACE);
|
|
}
|
|
|
|
|
|
static void find_clump_space(thread_db* tdbb,
|
|
WIN* window,
|
|
PAG* ppage,
|
|
USHORT type,
|
|
USHORT len,
|
|
const UCHAR* entry) //USHORT must_write
|
|
{
|
|
/***********************************************
|
|
*
|
|
* f i n d _ c l u m p _ s p a c e
|
|
*
|
|
***********************************************
|
|
*
|
|
* Functional description
|
|
* Find space for the new clump.
|
|
* Add the entry at the end of clumplet list.
|
|
* Allocate a new page if required.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
pag* page = *ppage;
|
|
header_page* header = 0; // used after the loop
|
|
|
|
while (true)
|
|
{
|
|
header = (header_page*) page;
|
|
const SLONG next_page = header->hdr_next_page;
|
|
const SLONG free_space = dbb->dbb_page_size - header->hdr_end;
|
|
USHORT* const end_addr = &header->hdr_end;
|
|
UCHAR* p = (UCHAR*) header + header->hdr_end;
|
|
|
|
if (free_space > (2 + len))
|
|
{
|
|
CCH_MARK_MUST_WRITE(tdbb, window);
|
|
|
|
fb_assert(type <= MAX_UCHAR);
|
|
fb_assert(len <= MAX_UCHAR);
|
|
*p++ = static_cast<UCHAR>(type);
|
|
*p++ = static_cast<UCHAR>(len);
|
|
|
|
if (len)
|
|
{
|
|
memcpy(p, entry, len);
|
|
p += len;
|
|
}
|
|
|
|
*p = HDR_end;
|
|
|
|
*end_addr = (USHORT) (p - (UCHAR*) page);
|
|
return;
|
|
}
|
|
|
|
if (!next_page)
|
|
break;
|
|
|
|
// Follow chain of header pages
|
|
|
|
*ppage = page = CCH_HANDOFF(tdbb, window, next_page, LCK_write, pag_header);
|
|
}
|
|
|
|
WIN new_window(DB_PAGE_SPACE, -1);
|
|
pag* new_page = (PAG) DPM_allocate(tdbb, &new_window);
|
|
|
|
CCH_MARK_MUST_WRITE(tdbb, &new_window);
|
|
|
|
header_page* const new_header = (header_page*) new_page;
|
|
new_header->hdr_header.pag_type = pag_header;
|
|
new_header->hdr_end = HDR_SIZE;
|
|
new_header->hdr_page_size = dbb->dbb_page_size;
|
|
new_header->hdr_data[0] = HDR_end;
|
|
const SLONG next_page = new_window.win_page.getPageNum();
|
|
USHORT* const end_addr = &new_header->hdr_end;
|
|
UCHAR* p = new_header->hdr_data;
|
|
|
|
fb_assert(type <= MAX_UCHAR);
|
|
fb_assert(len <= MAX_UCHAR);
|
|
*p++ = static_cast<UCHAR>(type);
|
|
*p++ = static_cast<UCHAR>(len);
|
|
|
|
if (len)
|
|
{
|
|
memcpy(p, entry, len);
|
|
p += len;
|
|
}
|
|
|
|
*p = HDR_end;
|
|
*end_addr = (USHORT) (p - (UCHAR*) new_page);
|
|
|
|
CCH_RELEASE(tdbb, &new_window);
|
|
|
|
CCH_precedence(tdbb, window, next_page);
|
|
|
|
CCH_MARK(tdbb, window);
|
|
|
|
header->hdr_next_page = next_page;
|
|
}
|
|
|
|
|
|
static bool find_type(thread_db* tdbb,
|
|
WIN* window,
|
|
PAG* ppage,
|
|
USHORT lock,
|
|
USHORT type,
|
|
UCHAR** entry_p,
|
|
const UCHAR** clump_end)
|
|
{
|
|
/***********************************************
|
|
*
|
|
* f i n d _ t y p e
|
|
*
|
|
***********************************************
|
|
*
|
|
* Functional description
|
|
* Find the requested type in a page.
|
|
* RETURNS
|
|
* pointer to type, pointer to end of page, header.
|
|
* true - Found it
|
|
* false - Not present
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
while (true)
|
|
{
|
|
header_page* header = (header_page*) (*ppage);
|
|
UCHAR* p = header->hdr_data;
|
|
const SLONG next_page = header->hdr_next_page;
|
|
|
|
UCHAR* q = 0;
|
|
for (; (*p != HDR_end); p += 2u + p[1])
|
|
{
|
|
if (*p == type)
|
|
q = p;
|
|
}
|
|
|
|
if (q)
|
|
{
|
|
*entry_p = q;
|
|
*clump_end = p;
|
|
return true;
|
|
}
|
|
|
|
// Follow chain of pages
|
|
|
|
if (next_page)
|
|
*ppage = CCH_HANDOFF(tdbb, window, next_page, lock, pag_header);
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// Class PageSpace starts here
|
|
|
|
PageSpace::~PageSpace()
|
|
{
|
|
if (file)
|
|
{
|
|
PIO_close(file);
|
|
|
|
while (file)
|
|
{
|
|
jrd_file* next = file->fil_next;
|
|
delete file;
|
|
file = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
ULONG PageSpace::actAlloc()
|
|
{
|
|
/**************************************
|
|
*
|
|
* Functional description
|
|
* Compute actual number of physically allocated pages of database.
|
|
*
|
|
**************************************/
|
|
|
|
// Traverse the linked list of files and add up the
|
|
// number of pages in each file
|
|
const USHORT pageSize = dbb->dbb_page_size;
|
|
ULONG tot_pages = 0;
|
|
for (const jrd_file* f = file; f != NULL; f = f->fil_next) {
|
|
tot_pages += PIO_get_number_of_pages(f, pageSize);
|
|
}
|
|
|
|
return tot_pages;
|
|
}
|
|
|
|
ULONG PageSpace::actAlloc(const Database* dbb)
|
|
{
|
|
PageSpace* pgSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
return pgSpace->actAlloc();
|
|
}
|
|
|
|
ULONG PageSpace::maxAlloc()
|
|
{
|
|
/**************************************
|
|
*
|
|
* Functional description
|
|
* Compute last physically allocated page of database.
|
|
*
|
|
**************************************/
|
|
const USHORT pageSize = dbb->dbb_page_size;
|
|
const jrd_file* f = file;
|
|
while (f->fil_next) {
|
|
f = f->fil_next;
|
|
}
|
|
|
|
const ULONG nPages = f->fil_min_page - f->fil_fudge + PIO_get_number_of_pages(f, pageSize);
|
|
|
|
if (maxPageNumber < nPages)
|
|
maxPageNumber = nPages;
|
|
|
|
return nPages;
|
|
}
|
|
|
|
ULONG PageSpace::maxAlloc(const Database* dbb)
|
|
{
|
|
PageSpace* pgSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
return pgSpace->maxAlloc();
|
|
}
|
|
|
|
ULONG PageSpace::lastUsedPage()
|
|
{
|
|
const PageManager& pageMgr = dbb->dbb_page_manager;
|
|
ULONG pipLast = (maxAlloc() / pageMgr.pagesPerPIP) * pageMgr.pagesPerPIP;
|
|
|
|
pipLast = pipLast ? pipLast - 1 : pipFirst;
|
|
win window(pageSpaceID, pipLast);
|
|
|
|
thread_db* tdbb = JRD_get_thread_data();
|
|
|
|
while (true)
|
|
{
|
|
pag* page = CCH_FETCH(tdbb, &window, LCK_read, pag_undefined);
|
|
if (page->pag_type == pag_pages)
|
|
break;
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
if (pipLast > pageMgr.pagesPerPIP)
|
|
pipLast -= pageMgr.pagesPerPIP;
|
|
else if (pipLast == pipFirst)
|
|
return 0; // can't find PIP page !
|
|
else
|
|
pipLast = pipFirst;
|
|
|
|
window.win_page = pipLast;
|
|
}
|
|
|
|
page_inv_page* pip = (page_inv_page*) window.win_buffer;
|
|
|
|
int last_bit = pip->pip_used;
|
|
int byte_num = last_bit / 8;
|
|
UCHAR mask = 1 << (last_bit % 8);
|
|
while (last_bit >= 0 && (pip->pip_bits[byte_num] & mask))
|
|
{
|
|
if (mask == 1)
|
|
{
|
|
mask = 0x80;
|
|
byte_num--;
|
|
//fb_assert(byte_num > -1); ???
|
|
}
|
|
else
|
|
mask >>= 1;
|
|
|
|
last_bit--;
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
if (pipLast == pipFirst)
|
|
return last_bit + 1;
|
|
|
|
return last_bit + pipLast + 1;
|
|
}
|
|
|
|
ULONG PageSpace::lastUsedPage(const Database* dbb)
|
|
{
|
|
PageSpace* pgSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
return pgSpace->lastUsedPage();
|
|
}
|
|
|
|
bool PageSpace::extend(thread_db* tdbb, const ULONG pageNum, const bool forceSize)
|
|
{
|
|
/**************************************
|
|
*
|
|
* Functional description
|
|
* Extend database file(s) up to at least pageNum pages. Number of pages to
|
|
* extend can't be less than hardcoded value MIN_EXTEND_BYTES and more than
|
|
* configured value "DatabaseGrowthIncrement" (both values in bytes).
|
|
*
|
|
* If "DatabaseGrowthIncrement" is less than MIN_EXTEND_BYTES then don't
|
|
* extend file(s)
|
|
*
|
|
* If forceSize is true, extend file up to pageNum pages (despite of value
|
|
* of "DatabaseGrowthIncrement") and don't make attempts to extend by less
|
|
* pages.
|
|
*
|
|
**************************************/
|
|
fb_assert(dbb == tdbb->getDatabase());
|
|
|
|
const int MAX_EXTEND_BYTES = dbb->dbb_config->getDatabaseGrowthIncrement();
|
|
|
|
if (pageNum < maxPageNumber || MAX_EXTEND_BYTES < MIN_EXTEND_BYTES && !forceSize)
|
|
return true;
|
|
|
|
if (pageNum >= maxAlloc())
|
|
{
|
|
const ULONG minExtendPages = MIN_EXTEND_BYTES / dbb->dbb_page_size;
|
|
const ULONG maxExtendPages = MAX_EXTEND_BYTES / dbb->dbb_page_size;
|
|
const ULONG reqPages = pageNum - maxPageNumber + 1;
|
|
|
|
ULONG extPages;
|
|
extPages = MIN(MAX(maxPageNumber / 16, minExtendPages), maxExtendPages);
|
|
extPages = MAX(reqPages, extPages);
|
|
|
|
while (true)
|
|
{
|
|
const ULONG oldMaxPageNumber = maxPageNumber;
|
|
try
|
|
{
|
|
PIO_extend(dbb, file, extPages, dbb->dbb_page_size);
|
|
break;
|
|
}
|
|
catch (const status_exception&)
|
|
{
|
|
if (extPages > reqPages && !forceSize)
|
|
{
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
|
|
// if file was extended, return, else try to extend by less pages
|
|
|
|
if (oldMaxPageNumber < maxAlloc())
|
|
return true;
|
|
|
|
extPages = MAX(reqPages, extPages / 2);
|
|
}
|
|
else
|
|
{
|
|
gds__log("Error extending file \"%s\" by %lu page(s).\nCurrently allocated %lu pages, requested page number %lu",
|
|
file->fil_string, extPages, maxPageNumber, pageNum);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
maxPageNumber = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ULONG PageSpace::getSCNPageNum(ULONG sequence)
|
|
{
|
|
/**************************************
|
|
*
|
|
* Functional description
|
|
* Return the physical number of the Nth SCN page
|
|
*
|
|
* SCN pages allocated at every pagesPerSCN pages in database and should
|
|
* not be the same as PIP page (which allocated at every pagesPerPIP pages).
|
|
* First SCN page number is fixed as FIRST_SCN_PAGE.
|
|
*
|
|
**************************************/
|
|
if (!sequence) {
|
|
return scnFirst;
|
|
}
|
|
return sequence * dbb->dbb_page_manager.pagesPerSCN;
|
|
}
|
|
|
|
ULONG PageSpace::getSCNPageNum(const Database* dbb, ULONG sequence)
|
|
{
|
|
PageSpace* pgSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
return pgSpace->getSCNPageNum(sequence);
|
|
}
|
|
|
|
PageSpace* PageManager::addPageSpace(const USHORT pageSpaceID)
|
|
{
|
|
PageSpace* newPageSpace = findPageSpace(pageSpaceID);
|
|
if (!newPageSpace)
|
|
{
|
|
newPageSpace = FB_NEW(pool) PageSpace(dbb, pageSpaceID);
|
|
pageSpaces.add(newPageSpace);
|
|
}
|
|
|
|
return newPageSpace;
|
|
}
|
|
|
|
PageSpace* PageManager::findPageSpace(const USHORT pageSpace) const
|
|
{
|
|
FB_SIZE_T pos;
|
|
if (pageSpaces.find(pageSpace, pos)) {
|
|
return pageSpaces[pos];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void PageManager::delPageSpace(const USHORT pageSpace)
|
|
{
|
|
FB_SIZE_T pos;
|
|
if (pageSpaces.find(pageSpace, pos))
|
|
{
|
|
PageSpace* pageSpaceToDelete = pageSpaces[pos];
|
|
pageSpaces.remove(pos);
|
|
delete pageSpaceToDelete;
|
|
}
|
|
}
|
|
|
|
void PageManager::closeAll()
|
|
{
|
|
for (FB_SIZE_T i = 0; i < pageSpaces.getCount(); i++)
|
|
{
|
|
if (pageSpaces[i]->file) {
|
|
PIO_close(pageSpaces[i]->file);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PageManager::initTempPageSpace(thread_db* tdbb)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
|
|
fb_assert(tempPageSpaceID == 0);
|
|
|
|
if (dbb->dbb_config->getSharedDatabase())
|
|
{
|
|
Jrd::Attachment* const attachment = tdbb->getAttachment();
|
|
|
|
if (!attachment->att_temp_pg_lock)
|
|
{
|
|
Lock* lock = FB_NEW_RPT(*attachment->att_pool, 0) Lock(tdbb, sizeof(SLONG), LCK_page_space);
|
|
|
|
while (true)
|
|
{
|
|
const double tmp = rand() * (MAX_USHORT - TEMP_PAGE_SPACE - 1.0) / (RAND_MAX + 1.0);
|
|
lock->lck_key.lck_long = static_cast<SLONG>(tmp) + TEMP_PAGE_SPACE + 1;
|
|
if (LCK_lock(tdbb, lock, LCK_write, LCK_NO_WAIT))
|
|
break;
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
}
|
|
|
|
attachment->att_temp_pg_lock = lock;
|
|
}
|
|
|
|
tempPageSpaceID = (USHORT) attachment->att_temp_pg_lock->lck_key.lck_long;
|
|
}
|
|
else
|
|
{
|
|
tempPageSpaceID = TEMP_PAGE_SPACE;
|
|
}
|
|
|
|
addPageSpace(tempPageSpaceID);
|
|
}
|
|
|
|
USHORT PageManager::getTempPageSpaceID(thread_db* tdbb)
|
|
{
|
|
fb_assert(tempPageSpaceID != 0);
|
|
if (!tempFileCreated)
|
|
{
|
|
Firebird::MutexLockGuard guard(initTmpMtx, FB_FUNCTION);
|
|
if (!tempFileCreated)
|
|
{
|
|
PageSpace* pageSpaceTemp = dbb->dbb_page_manager.findPageSpace(tempPageSpaceID);
|
|
|
|
PathName file_name = TempFile::create(SCRATCH);
|
|
pageSpaceTemp->file = PIO_create(dbb, file_name, true, true);
|
|
PAG_format_pip(tdbb, *pageSpaceTemp);
|
|
|
|
tempFileCreated = true;
|
|
}
|
|
}
|
|
return tempPageSpaceID;
|
|
};
|
|
|
|
ULONG PAG_page_count(Database* database, PageCountCallback* cb)
|
|
{
|
|
/*********************************************
|
|
*
|
|
* P A G _ p a g e _ c o u n t
|
|
*
|
|
*********************************************
|
|
*
|
|
* Functional description
|
|
* Count pages, used by database
|
|
*
|
|
*********************************************/
|
|
fb_assert(cb);
|
|
|
|
Array<UCHAR> temp;
|
|
page_inv_page* pip = reinterpret_cast<Ods::page_inv_page*>
|
|
(FB_ALIGN(temp.getBuffer(database->dbb_page_size + MIN_PAGE_SIZE), MIN_PAGE_SIZE));
|
|
|
|
PageSpace* pageSpace = database->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
|
|
fb_assert(pageSpace);
|
|
|
|
ULONG pageNo = pageSpace->pipFirst;
|
|
const ULONG pagesPerPip = database->dbb_page_manager.pagesPerPIP;
|
|
|
|
for (ULONG sequence = 0; true; pageNo = (pagesPerPip * ++sequence) - 1)
|
|
{
|
|
cb->newPage(pageNo, &pip->pip_header);
|
|
fb_assert(pip->pip_header.pag_type == pag_pages);
|
|
if (pip->pip_used == pagesPerPip)
|
|
{
|
|
// this is not last page, continue search
|
|
continue;
|
|
}
|
|
|
|
return pip->pip_used + pageNo + (sequence ? 1 : -1);
|
|
}
|
|
|
|
// compiler warnings silencer
|
|
return 0;
|
|
}
|
|
|
|
void PAG_set_page_scn(thread_db* tdbb, win* window)
|
|
{
|
|
Database* dbb = tdbb->getDatabase();
|
|
fb_assert(dbb->dbb_ods_version >= ODS_VERSION12);
|
|
|
|
PageManager& pageMgr = dbb->dbb_page_manager;
|
|
PageSpace* pageSpace = pageMgr.findPageSpace(window->win_page.getPageSpaceID());
|
|
|
|
if (pageSpace->isTemporary())
|
|
return;
|
|
|
|
const ULONG curr_scn = window->win_buffer->pag_scn;
|
|
const ULONG page_num = window->win_page.getPageNum();
|
|
const ULONG scn_seq = page_num / pageMgr.pagesPerSCN;
|
|
const ULONG scn_slot = page_num % pageMgr.pagesPerSCN;
|
|
|
|
const ULONG scn_page = pageSpace->getSCNPageNum(scn_seq);
|
|
|
|
if (scn_page == page_num)
|
|
{
|
|
scns_page* page = (scns_page*) window->win_buffer;
|
|
page->scn_pages[scn_slot] = curr_scn;
|
|
return;
|
|
}
|
|
|
|
win scn_window(pageSpace->pageSpaceID, scn_page);
|
|
|
|
scns_page* page = (scns_page*) CCH_FETCH(tdbb, &scn_window, LCK_write, pag_scns);
|
|
CCH_MARK(tdbb, &scn_window);
|
|
page->scn_pages[scn_slot] = curr_scn;
|
|
CCH_RELEASE(tdbb, &scn_window);
|
|
|
|
CCH_precedence(tdbb, window, scn_page);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
namespace
|
|
{
|
|
// This checks should better be placed at ods.h but we can't use fb_assert() there.
|
|
// See also comments in ods.h near the scns_page definition.
|
|
|
|
class CheckODS
|
|
{
|
|
public:
|
|
CheckODS()
|
|
{
|
|
for (ULONG page_size = MIN_PAGE_SIZE; page_size <= MAX_PAGE_SIZE; page_size *= 2)
|
|
{
|
|
ULONG pagesPerPIP = Ods::pagesPerPIP(page_size);
|
|
ULONG pagesPerSCN = Ods::pagesPerSCN(page_size);
|
|
ULONG maxPagesPerSCN = Ods::maxPagesPerSCN(page_size);
|
|
|
|
fb_assert((pagesPerPIP % pagesPerSCN) == 0);
|
|
fb_assert(pagesPerSCN <= maxPagesPerSCN);
|
|
}
|
|
}
|
|
};
|
|
|
|
static CheckODS doCheck;
|
|
}
|
|
#endif // DEBUG
|