2001-05-23 15:26:42 +02:00
|
|
|
/*
|
|
|
|
* PROGRAM: JRD Access Method
|
2003-10-29 11:53:47 +01:00
|
|
|
* MODULE: winnt.cpp
|
2001-05-23 15:26:42 +02:00
|
|
|
* DESCRIPTION: Windows NT specific physical IO
|
|
|
|
*
|
|
|
|
* 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): ______________________________________.
|
2001-07-10 19:35:13 +02:00
|
|
|
* 2001.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
|
|
|
|
* conditionals, as the engine now fully supports
|
|
|
|
* readonly databases.
|
2001-12-24 03:51:06 +01:00
|
|
|
*
|
2002-07-01 17:07:18 +02:00
|
|
|
*
|
|
|
|
* 17-Oct-2001 Mike Nordell: Non-shared file access
|
|
|
|
*
|
2001-12-24 03:51:06 +01:00
|
|
|
* 02-Nov-2001 Mike Nordell: Synch with FB1 changes.
|
|
|
|
*
|
|
|
|
* 20-Nov-2001 Ann Harrison: Make page count work on db with forced write
|
|
|
|
*
|
|
|
|
* 21-Nov-2001 Ann Harrison: Allow read sharing so gstat works
|
2001-05-23 15:26:42 +02:00
|
|
|
*/
|
|
|
|
|
2001-07-29 19:42:23 +02:00
|
|
|
#include "firebird.h"
|
2001-05-23 15:26:42 +02:00
|
|
|
#include <string.h>
|
2004-03-22 12:38:23 +01:00
|
|
|
#include "../jrd/common.h"
|
2001-05-23 15:26:42 +02:00
|
|
|
#include "../jrd/jrd.h"
|
2003-07-14 12:35:49 +02:00
|
|
|
#include "../jrd/os/pio.h"
|
2001-05-23 15:26:42 +02:00
|
|
|
#include "../jrd/ods.h"
|
|
|
|
#include "../jrd/lck.h"
|
|
|
|
#include "../jrd/cch.h"
|
2003-11-11 13:19:20 +01:00
|
|
|
#include "gen/iberror.h"
|
2001-05-23 15:26:42 +02:00
|
|
|
#include "../jrd/all_proto.h"
|
|
|
|
#include "../jrd/cch_proto.h"
|
|
|
|
#include "../jrd/err_proto.h"
|
|
|
|
#include "../jrd/isc_proto.h"
|
|
|
|
#include "../jrd/isc_f_proto.h"
|
|
|
|
|
|
|
|
#include "../jrd/lck_proto.h"
|
|
|
|
#include "../jrd/mov_proto.h"
|
2003-07-14 12:35:49 +02:00
|
|
|
#include "../jrd/os/pio_proto.h"
|
2004-06-08 15:41:08 +02:00
|
|
|
#include "../jrd/thd.h"
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
|
|
|
|
2004-03-20 15:57:40 +01:00
|
|
|
using namespace Jrd;
|
|
|
|
|
2001-05-23 15:26:42 +02:00
|
|
|
#ifdef TEXT
|
|
|
|
#undef TEXT
|
|
|
|
#endif
|
|
|
|
#define TEXT SCHAR
|
|
|
|
|
2004-05-14 01:20:50 +02:00
|
|
|
const USHORT OS_WINDOWS_NT = 1;
|
|
|
|
const USHORT OS_CHICAGO = 2;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
#ifdef SUPERSERVER_V2
|
2004-02-20 07:43:27 +01:00
|
|
|
static void release_io_event(jrd_file*, OVERLAPPED*);
|
2001-05-23 15:26:42 +02:00
|
|
|
#endif
|
2004-02-20 07:43:27 +01:00
|
|
|
static ULONG get_number_of_pages(const jrd_file*, const USHORT);
|
2003-10-29 11:53:47 +01:00
|
|
|
static bool MaybeCloseFile(SLONG*);
|
2004-03-18 06:56:06 +01:00
|
|
|
static jrd_file* seek_file(jrd_file*, BufferDesc*, ISC_STATUS*, OVERLAPPED*, OVERLAPPED**);
|
2004-03-07 08:58:55 +01:00
|
|
|
static jrd_file* setup_file(Database*, const TEXT*, USHORT, HANDLE);
|
2004-02-20 07:43:27 +01:00
|
|
|
static bool nt_error(TEXT*, const jrd_file*, ISC_STATUS, ISC_STATUS*);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
static USHORT ostype;
|
|
|
|
|
2001-11-02 21:59:38 +01:00
|
|
|
#ifdef SUPERSERVER_V2
|
2001-12-24 03:51:06 +01:00
|
|
|
static const DWORD g_dwShareFlags = FILE_SHARE_READ; // no write sharing
|
2001-11-02 21:59:38 +01:00
|
|
|
static const DWORD g_dwExtraFlags = FILE_FLAG_OVERLAPPED |
|
|
|
|
FILE_FLAG_NO_BUFFERING |
|
|
|
|
FILE_FLAG_RANDOM_ACCESS;
|
2002-04-03 10:41:23 +02:00
|
|
|
#else
|
|
|
|
#ifdef SUPERSERVER
|
2001-12-24 03:51:06 +01:00
|
|
|
static const DWORD g_dwShareFlags = FILE_SHARE_READ; // no write sharing
|
2001-11-02 21:59:38 +01:00
|
|
|
static const DWORD g_dwExtraFlags = FILE_FLAG_RANDOM_ACCESS;
|
|
|
|
#else
|
|
|
|
static const DWORD g_dwShareFlags = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
|
|
|
static const DWORD g_dwExtraFlags = FILE_FLAG_RANDOM_ACCESS;
|
|
|
|
#endif
|
2002-04-03 10:41:23 +02:00
|
|
|
#endif
|
2001-11-02 21:59:38 +01:00
|
|
|
|
|
|
|
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
int PIO_add_file(Database* dbb, jrd_file* main_file, const TEXT* file_name, SLONG start)
|
2002-11-10 16:38:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
/**************************************
|
|
|
|
*
|
2002-11-10 16:38:38 +01:00
|
|
|
* P I O _ a d d _ f i l e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
2001-05-23 15:26:42 +02:00
|
|
|
* Add a file to an existing database. Return the sequence
|
|
|
|
* number of the new file. If anything goes wrong, return a
|
|
|
|
* sequence of 0.
|
|
|
|
*
|
|
|
|
**************************************/
|
2004-02-20 07:43:27 +01:00
|
|
|
jrd_file* new_file = PIO_create(dbb, file_name, strlen(file_name), false);
|
2001-05-23 15:26:42 +02:00
|
|
|
if (!new_file) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
new_file->fil_min_page = start;
|
2003-10-29 11:53:47 +01:00
|
|
|
USHORT sequence = 1;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
jrd_file* file;
|
2001-05-23 15:26:42 +02:00
|
|
|
for (file = main_file; file->fil_next; file = file->fil_next) {
|
|
|
|
++sequence;
|
|
|
|
}
|
|
|
|
|
|
|
|
file->fil_max_page = start - 1;
|
|
|
|
file->fil_next = new_file;
|
|
|
|
|
|
|
|
return sequence;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
void PIO_close(jrd_file* main_file)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ c l o s e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
*
|
|
|
|
**************************************/
|
2004-02-20 07:43:27 +01:00
|
|
|
for (jrd_file* file = main_file; file; file = file->fil_next)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
2001-11-02 21:59:38 +01:00
|
|
|
if (MaybeCloseFile(&file->fil_desc) ||
|
|
|
|
MaybeCloseFile(&file->fil_force_write_desc))
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
#ifdef SUPERSERVER_V2
|
2002-04-29 17:05:11 +02:00
|
|
|
for (int i = 0; i < MAX_FILE_IO; i++)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
if (file->fil_io_events[i])
|
|
|
|
{
|
|
|
|
CloseHandle((HANDLE) file->fil_io_events[i]);
|
2001-11-02 21:59:38 +01:00
|
|
|
file->fil_io_events[i] = 0;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
int PIO_connection(const TEXT* file_name, USHORT* file_length)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ c o n n e c t i o n
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Analyze a file specification and determine whether a page/lock
|
|
|
|
* server is required and available. If so, return a "connection"
|
|
|
|
* block. If not, return NULL.
|
|
|
|
*
|
|
|
|
* Note: The file name must have been expanded prior to this call.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
jrd_file* PIO_create(Database* dbb, const TEXT* string, SSHORT length, bool overwrite)
|
2002-11-10 16:38:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
/**************************************
|
|
|
|
*
|
2002-11-10 16:38:38 +01:00
|
|
|
* P I O _ c r e a t e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
2001-05-23 15:26:42 +02:00
|
|
|
* Create a new database file.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
TEXT workspace[MAXPATHLEN];
|
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
const TEXT* file_name = string;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2001-11-02 21:59:38 +01:00
|
|
|
if (length)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
MOVE_FAST(file_name, workspace, length);
|
|
|
|
workspace[length] = 0;
|
|
|
|
file_name = workspace;
|
|
|
|
}
|
|
|
|
|
2003-12-31 06:36:12 +01:00
|
|
|
const HANDLE desc = CreateFile(file_name,
|
2001-05-23 15:26:42 +02:00
|
|
|
GENERIC_READ | GENERIC_WRITE,
|
2001-11-02 21:59:38 +01:00
|
|
|
g_dwShareFlags,
|
2001-05-23 15:26:42 +02:00
|
|
|
NULL,
|
|
|
|
(overwrite ? CREATE_ALWAYS : CREATE_NEW),
|
|
|
|
FILE_ATTRIBUTE_NORMAL |
|
2001-11-02 21:59:38 +01:00
|
|
|
g_dwExtraFlags,
|
|
|
|
0);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2001-11-02 21:59:38 +01:00
|
|
|
if (desc == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
ERR_post(isc_io_error,
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_arg_string, "CreateFile (create)",
|
|
|
|
isc_arg_cstring,
|
2001-05-23 15:26:42 +02:00
|
|
|
length,
|
|
|
|
ERR_string(string, length),
|
|
|
|
isc_arg_gds,
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_io_create_err, isc_arg_win32, GetLastError(), 0);
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* File open succeeded. Now expand the file name. */
|
|
|
|
/* workspace is the exapnded name here */
|
|
|
|
|
2004-10-08 06:48:02 +02:00
|
|
|
length = PIO_expand(string, length, workspace, sizeof(workspace));
|
2004-02-25 07:33:26 +01:00
|
|
|
jrd_file *file;
|
|
|
|
try {
|
|
|
|
file = setup_file(dbb, workspace, length, desc);
|
|
|
|
} catch(const std::exception&) {
|
|
|
|
CloseHandle(desc);
|
|
|
|
throw;
|
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-11-08 07:16:19 +01:00
|
|
|
int PIO_expand(const TEXT* file_name, USHORT file_length, TEXT* expanded_name,
|
|
|
|
size_t len_expanded)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ e x p a n d
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Fully expand a file name. If the file doesn't exist, do something
|
|
|
|
* intelligent.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
|
2004-11-07 15:43:29 +01:00
|
|
|
return ISC_expand_filename(file_name, file_length,
|
|
|
|
expanded_name, len_expanded, false);
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
void PIO_flush(jrd_file* main_file)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ f l u s h
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Flush the operating system cache back to good, solid oxide.
|
|
|
|
*
|
|
|
|
**************************************/
|
2004-02-20 07:43:27 +01:00
|
|
|
for (jrd_file* file = main_file; file; file = file->fil_next)
|
2001-11-02 21:59:38 +01:00
|
|
|
{
|
2003-02-05 21:43:01 +01:00
|
|
|
if (ostype == OS_CHICAGO)
|
2001-11-02 21:59:38 +01:00
|
|
|
{
|
2003-02-05 21:43:01 +01:00
|
|
|
THD_MUTEX_LOCK(file->fil_mutex);
|
|
|
|
}
|
|
|
|
FlushFileBuffers((HANDLE) file->fil_desc);
|
|
|
|
if (ostype == OS_CHICAGO)
|
|
|
|
{
|
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2001-11-02 21:59:38 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
void PIO_force_write(jrd_file* file, bool flag)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ f o r c e _ w r i t e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Set (or clear) force write, if possible, for the database.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
|
2001-12-28 06:16:58 +01:00
|
|
|
const bool bOldForce = (file->fil_flags & FIL_force_write_init) != 0;
|
|
|
|
|
2002-07-01 17:07:18 +02:00
|
|
|
if ((flag && !bOldForce) || (!flag && bOldForce)) {
|
2001-12-28 06:16:58 +01:00
|
|
|
SLONG& hOld = flag ? file->fil_desc : file->fil_force_write_desc;
|
|
|
|
HANDLE& hNew = reinterpret_cast<HANDLE&>(flag ? file->fil_force_write_desc : file->fil_desc);
|
2003-10-29 11:53:47 +01:00
|
|
|
const int force = flag ? FILE_FLAG_WRITE_THROUGH : 0;
|
2001-12-28 06:16:58 +01:00
|
|
|
|
|
|
|
MaybeCloseFile(&hOld);
|
|
|
|
hNew = CreateFile(file->fil_string,
|
|
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
|
|
g_dwShareFlags,
|
|
|
|
NULL,
|
|
|
|
OPEN_EXISTING,
|
2002-07-01 17:07:18 +02:00
|
|
|
FILE_ATTRIBUTE_NORMAL | force | g_dwExtraFlags,
|
2001-12-28 06:16:58 +01:00
|
|
|
0);
|
|
|
|
|
|
|
|
if (hNew == INVALID_HANDLE_VALUE)
|
2001-11-02 21:59:38 +01:00
|
|
|
{
|
2001-12-28 06:16:58 +01:00
|
|
|
ERR_post(isc_io_error,
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_arg_string,
|
2001-12-28 06:16:58 +01:00
|
|
|
"CreateFile (force write)",
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_arg_cstring,
|
2001-12-28 06:16:58 +01:00
|
|
|
file->fil_length,
|
|
|
|
ERR_string(file->fil_string, file->fil_length),
|
|
|
|
isc_arg_gds, isc_io_access_err,
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_arg_win32,
|
2001-12-28 06:16:58 +01:00
|
|
|
GetLastError(),
|
|
|
|
0);
|
|
|
|
}
|
2003-02-05 21:43:01 +01:00
|
|
|
|
|
|
|
if (flag) {
|
|
|
|
file->fil_flags |= (FIL_force_write | FIL_force_write_init);
|
2004-01-28 08:50:41 +01:00
|
|
|
}
|
|
|
|
else {
|
2003-02-05 21:43:01 +01:00
|
|
|
file->fil_flags &= ~FIL_force_write;
|
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
void PIO_header(Database* dbb, SCHAR * address, int length)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ h e a d e r
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Read the page header. This assumes that the file has not been
|
|
|
|
* repositioned since the file was originally mapped.
|
2004-02-25 07:33:26 +01:00
|
|
|
* The detail of Win32 implementation is that it doesn't assume
|
|
|
|
* this fact as seeks to first byte of file initially, but external
|
|
|
|
* callers should not rely on this behavior
|
2001-05-23 15:26:42 +02:00
|
|
|
*
|
|
|
|
**************************************/
|
2004-02-20 07:43:27 +01:00
|
|
|
jrd_file* file = dbb->dbb_file;
|
2003-10-29 11:53:47 +01:00
|
|
|
HANDLE desc = (HANDLE) ((file->fil_flags & FIL_force_write) ?
|
2001-05-23 15:26:42 +02:00
|
|
|
file->fil_force_write_desc : file->fil_desc);
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
OVERLAPPED overlapped, *overlapped_ptr;
|
2001-11-02 21:59:38 +01:00
|
|
|
if (ostype == OS_CHICAGO)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_LOCK(file->fil_mutex);
|
2003-08-09 21:01:02 +02:00
|
|
|
if (SetFilePointer(desc, 0, NULL, FILE_BEGIN) == (DWORD) -1)
|
2001-11-02 21:59:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
|
|
|
nt_error("SetFilePointer", file, isc_io_read_err, 0);
|
|
|
|
}
|
|
|
|
overlapped_ptr = NULL;
|
|
|
|
}
|
2001-11-02 21:59:38 +01:00
|
|
|
else
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
memset(&overlapped, 0, sizeof(OVERLAPPED));
|
|
|
|
overlapped_ptr = &overlapped;
|
|
|
|
#ifdef SUPERSERVER_V2
|
|
|
|
if (!(overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
|
|
|
|
nt_error("Overlapped event", file, isc_io_read_err, 0);
|
|
|
|
ResetEvent(overlapped.hEvent);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
DWORD actual_length;
|
2004-05-27 18:26:52 +02:00
|
|
|
if (dbb->dbb_encrypt_key.hasData())
|
2001-11-02 21:59:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
SLONG spare_buffer[MAX_PAGE_SIZE / sizeof(SLONG)];
|
|
|
|
|
2003-02-09 10:52:32 +01:00
|
|
|
if (!ReadFile(desc, spare_buffer, length, &actual_length, overlapped_ptr)
|
2001-11-02 21:59:38 +01:00
|
|
|
|| actual_length != (DWORD)length)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
if (ostype == OS_CHICAGO)
|
2001-11-02 21:59:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2001-11-02 21:59:38 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
nt_error("ReadFile", file, isc_io_read_err, 0);
|
|
|
|
}
|
|
|
|
|
2004-03-14 14:40:14 +01:00
|
|
|
(*dbb->dbb_decrypt) (dbb->dbb_encrypt_key.c_str(),
|
2001-05-23 15:26:42 +02:00
|
|
|
spare_buffer, length, address);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2003-02-09 10:52:32 +01:00
|
|
|
if (!ReadFile(desc, address, length, &actual_length, overlapped_ptr)
|
2001-11-02 21:59:38 +01:00
|
|
|
|| actual_length != (DWORD)length)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
#ifdef SUPERSERVER_V2
|
2003-02-09 10:52:32 +01:00
|
|
|
if (!GetOverlappedResult(desc, overlapped_ptr, &actual_length, TRUE)
|
2001-11-02 21:59:38 +01:00
|
|
|
|| actual_length != length)
|
|
|
|
{
|
|
|
|
CloseHandle(overlapped.hEvent);
|
|
|
|
nt_error("GetOverlappedResult", file, isc_io_read_err, 0);
|
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
#else
|
|
|
|
if (ostype == OS_CHICAGO)
|
2001-11-02 21:59:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2001-11-02 21:59:38 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
nt_error("ReadFile", file, isc_io_read_err, 0);
|
|
|
|
#endif
|
2001-11-02 21:59:38 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef SUPERSERVER_V2
|
|
|
|
CloseHandle(overlapped.hEvent);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (ostype == OS_CHICAGO) {
|
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
SLONG PIO_max_alloc(Database* dbb)
|
2002-11-10 16:38:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
/**************************************
|
|
|
|
*
|
2002-11-10 16:38:38 +01:00
|
|
|
* P I O _ m a x _ a l l o c
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
2001-05-23 15:26:42 +02:00
|
|
|
* Compute last physically allocated page of database.
|
|
|
|
*
|
|
|
|
**************************************/
|
2004-02-20 07:43:27 +01:00
|
|
|
jrd_file* file = dbb->dbb_file;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
while (file->fil_next) {
|
|
|
|
file = file->fil_next;
|
|
|
|
}
|
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
const ULONG nPages = get_number_of_pages(file, dbb->dbb_page_size);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
return file->fil_min_page - file->fil_fudge + nPages;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
SLONG PIO_act_alloc(Database* dbb)
|
2002-11-10 16:38:38 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
/**************************************
|
|
|
|
*
|
2002-11-10 16:38:38 +01:00
|
|
|
* P I O _ a c t _ a l l o c
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
2001-05-23 15:26:42 +02:00
|
|
|
* Compute actual number of physically allocated pages of database.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
SLONG tot_pages = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
** Traverse the linked list of files and add up the number of pages
|
|
|
|
** in each file
|
|
|
|
**/
|
2004-02-20 07:43:27 +01:00
|
|
|
for (const jrd_file* file = dbb->dbb_file; file != NULL; file = file->fil_next) {
|
2001-12-24 03:51:06 +01:00
|
|
|
tot_pages += get_number_of_pages(file, dbb->dbb_page_size);
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return tot_pages;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
jrd_file* PIO_open(Database* dbb,
|
2003-10-29 11:53:47 +01:00
|
|
|
const TEXT* string,
|
2001-05-23 15:26:42 +02:00
|
|
|
SSHORT length,
|
2004-02-20 07:43:27 +01:00
|
|
|
bool trace_flag,
|
|
|
|
blk* connection, const TEXT* file_name, USHORT file_length)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
2002-11-10 16:38:38 +01:00
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ o p e n
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Open a database file. If a "connection"
|
|
|
|
* block is provided, use the connection
|
2002-12-06 12:44:37 +01:00
|
|
|
* to communicate with a page/lock server.
|
2002-11-10 16:38:38 +01:00
|
|
|
*
|
|
|
|
**************************************/
|
2003-10-29 11:53:47 +01:00
|
|
|
TEXT temp[MAXPATHLEN];
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
const TEXT* ptr;
|
2001-05-23 15:26:42 +02:00
|
|
|
if (string) {
|
|
|
|
ptr = string;
|
|
|
|
if (length) {
|
|
|
|
MOVE_FAST(string, temp, length);
|
|
|
|
temp[length] = 0;
|
|
|
|
ptr = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ptr = file_name;
|
|
|
|
if (file_length) {
|
|
|
|
MOVE_FAST(file_name, temp, file_length);
|
|
|
|
temp[file_length] = 0;
|
|
|
|
ptr = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
HANDLE desc = CreateFile(ptr,
|
2001-05-23 15:26:42 +02:00
|
|
|
GENERIC_READ | GENERIC_WRITE,
|
2001-11-02 21:59:38 +01:00
|
|
|
g_dwShareFlags,
|
2001-05-23 15:26:42 +02:00
|
|
|
NULL,
|
|
|
|
OPEN_EXISTING,
|
|
|
|
FILE_ATTRIBUTE_NORMAL |
|
2001-11-02 21:59:38 +01:00
|
|
|
g_dwExtraFlags,
|
|
|
|
0);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
if (desc == INVALID_HANDLE_VALUE) {
|
|
|
|
/* Try opening the database file in ReadOnly mode.
|
|
|
|
* The database file could be on a RO medium (CD-ROM etc.).
|
|
|
|
* If this fileopen fails, return error.
|
|
|
|
*/
|
|
|
|
desc = CreateFile(ptr,
|
|
|
|
GENERIC_READ,
|
|
|
|
FILE_SHARE_READ,
|
|
|
|
NULL,
|
|
|
|
OPEN_EXISTING,
|
|
|
|
FILE_ATTRIBUTE_NORMAL |
|
2001-11-02 21:59:38 +01:00
|
|
|
g_dwExtraFlags, 0);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
if (desc == INVALID_HANDLE_VALUE) {
|
|
|
|
ERR_post(isc_io_error,
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_arg_string,
|
2001-05-23 15:26:42 +02:00
|
|
|
"CreateFile (open)",
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_arg_cstring,
|
2001-05-23 15:26:42 +02:00
|
|
|
file_length,
|
|
|
|
ERR_string(file_name, file_length),
|
|
|
|
isc_arg_gds,
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_io_open_err, isc_arg_win32, GetLastError(), 0);
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
else {
|
2004-03-07 08:58:55 +01:00
|
|
|
/* If this is the primary file, set Database flag to indicate that it is
|
2001-05-23 15:26:42 +02:00
|
|
|
* being opened ReadOnly. This flag will be used later to compare with
|
|
|
|
* the Header Page flag setting to make sure that the database is set
|
|
|
|
* ReadOnly.
|
|
|
|
*/
|
|
|
|
if (!dbb->dbb_file)
|
|
|
|
dbb->dbb_flags |= DBB_being_opened_read_only;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-02-25 07:33:26 +01:00
|
|
|
jrd_file *file;
|
|
|
|
try {
|
|
|
|
file = setup_file(dbb, string, length, desc);
|
|
|
|
} catch(const std::exception&) {
|
|
|
|
CloseHandle(desc);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
return file;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-20 15:57:40 +01:00
|
|
|
bool PIO_read(jrd_file* file, BufferDesc* bdb, Ods::pag* page, ISC_STATUS* status_vector)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ r e a d
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Read a data page.
|
|
|
|
*
|
|
|
|
**************************************/
|
2004-03-07 08:58:55 +01:00
|
|
|
Database* dbb = bdb->bdb_dbb;
|
2003-10-29 11:53:47 +01:00
|
|
|
const DWORD size = dbb->dbb_page_size;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
OVERLAPPED overlapped, *overlapped_ptr;
|
2002-04-29 17:05:11 +02:00
|
|
|
if (!(file = seek_file(file, bdb, status_vector, &overlapped, &overlapped_ptr)))
|
2004-02-20 07:43:27 +01:00
|
|
|
return false;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
HANDLE desc = (HANDLE) ((file->fil_flags & FIL_force_write) ?
|
2001-05-23 15:26:42 +02:00
|
|
|
file->fil_force_write_desc : file->fil_desc);
|
|
|
|
|
2004-05-27 18:26:52 +02:00
|
|
|
if (dbb->dbb_encrypt_key.hasData())
|
2001-12-24 03:51:06 +01:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
SLONG spare_buffer[MAX_PAGE_SIZE / sizeof(SLONG)];
|
2004-02-20 07:43:27 +01:00
|
|
|
DWORD actual_length;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2003-02-09 10:52:32 +01:00
|
|
|
if (!ReadFile(desc, spare_buffer, size, &actual_length, overlapped_ptr)
|
2001-12-24 03:51:06 +01:00
|
|
|
|| actual_length != size)
|
|
|
|
{
|
|
|
|
if (ostype == OS_CHICAGO) {
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2001-12-24 03:51:06 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
return nt_error("ReadFile", file, isc_io_read_err, status_vector);
|
|
|
|
}
|
|
|
|
|
2004-03-14 14:40:14 +01:00
|
|
|
(*dbb->dbb_decrypt) (dbb->dbb_encrypt_key.c_str(),
|
2001-05-23 15:26:42 +02:00
|
|
|
spare_buffer, size, page);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2004-02-20 07:43:27 +01:00
|
|
|
DWORD actual_length;
|
2001-05-23 15:26:42 +02:00
|
|
|
if (!ReadFile(desc, page, size, &actual_length, overlapped_ptr) ||
|
2001-11-02 21:59:38 +01:00
|
|
|
actual_length != size)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
#ifdef SUPERSERVER_V2
|
2003-02-09 10:52:32 +01:00
|
|
|
if (!GetOverlappedResult(desc, overlapped_ptr, &actual_length, TRUE)
|
2001-11-02 21:59:38 +01:00
|
|
|
|| actual_length != size)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
release_io_event(file, overlapped_ptr);
|
|
|
|
return nt_error("GetOverlappedResult", file, isc_io_read_err,
|
|
|
|
status_vector);
|
|
|
|
}
|
|
|
|
#else
|
2002-04-29 17:05:11 +02:00
|
|
|
if (ostype == OS_CHICAGO) {
|
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
2003-02-09 10:52:32 +01:00
|
|
|
return nt_error("ReadFile", file, isc_io_read_err, status_vector);
|
2001-05-23 15:26:42 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef SUPERSERVER_V2
|
|
|
|
release_io_event(file, overlapped_ptr);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (ostype == OS_CHICAGO) {
|
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
|
|
|
}
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
return true;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SUPERSERVER_V2
|
2004-03-07 08:58:55 +01:00
|
|
|
bool PIO_read_ahead(Database* dbb,
|
2001-12-24 03:51:06 +01:00
|
|
|
SLONG start_page,
|
|
|
|
SCHAR* buffer,
|
|
|
|
SLONG pages,
|
2004-02-20 07:43:27 +01:00
|
|
|
phys_io_blk* piob,
|
2003-04-10 08:49:16 +02:00
|
|
|
ISC_STATUS* status_vector)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ r e a d _ a h e a d
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Read a contiguous set of pages. The only
|
|
|
|
* tricky part is to segment the I/O when crossing
|
|
|
|
* file boundaries.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
OVERLAPPED overlapped, *overlapped_ptr;
|
|
|
|
|
|
|
|
/* If an I/O status block was passed the caller wants to
|
|
|
|
queue an asynchronous I/O. */
|
|
|
|
|
|
|
|
if (!piob) {
|
|
|
|
overlapped_ptr = &overlapped;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
overlapped_ptr = (OVERLAPPED *) & piob->piob_io_event;
|
|
|
|
piob->piob_flags = 0;
|
|
|
|
}
|
|
|
|
|
2004-03-18 06:56:06 +01:00
|
|
|
BufferDesc bdb;
|
2001-05-23 15:26:42 +02:00
|
|
|
while (pages) {
|
|
|
|
/* Setup up a dummy buffer descriptor block for seeking file. */
|
|
|
|
|
|
|
|
bdb.bdb_dbb = dbb;
|
|
|
|
bdb.bdb_page = start_page;
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
jrd_file* file = seek_file(dbb->dbb_file,
|
2001-12-24 03:51:06 +01:00
|
|
|
&bdb, status_vector,
|
|
|
|
overlapped_ptr,
|
|
|
|
&overlapped_ptr);
|
2001-05-23 15:26:42 +02:00
|
|
|
if (!file) {
|
2004-02-20 07:43:27 +01:00
|
|
|
return false;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Check that every page within the set resides in the same database
|
|
|
|
file. If not read what you can and loop back for the rest. */
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
DWORD segmented_length = 0;
|
2001-05-23 15:26:42 +02:00
|
|
|
while (pages && start_page >= file->fil_min_page
|
2004-02-20 07:43:27 +01:00
|
|
|
&& start_page <= file->fil_max_page)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
segmented_length += dbb->dbb_page_size;
|
|
|
|
++start_page;
|
|
|
|
--pages;
|
|
|
|
}
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
HANDLE desc = (HANDLE) ((file->fil_flags & FIL_force_write) ?
|
2001-05-23 15:26:42 +02:00
|
|
|
file->fil_force_write_desc : file->fil_desc);
|
|
|
|
|
2004-03-18 06:56:06 +01:00
|
|
|
DWORD actual_length;
|
2001-12-24 03:51:06 +01:00
|
|
|
if (ReadFile( desc,
|
|
|
|
buffer,
|
|
|
|
segmented_length,
|
|
|
|
&actual_length,
|
|
|
|
overlapped_ptr) &&
|
|
|
|
actual_length == segmented_length)
|
|
|
|
{
|
|
|
|
if (piob && !pages) {
|
2001-05-23 15:26:42 +02:00
|
|
|
piob->piob_flags = PIOB_success;
|
2001-12-24 03:51:06 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
else if (piob && !pages) {
|
|
|
|
piob->piob_flags = PIOB_pending;
|
2004-02-20 07:43:27 +01:00
|
|
|
piob->piob_desc = reinterpret_cast<SLONG>(desc);
|
2001-05-23 15:26:42 +02:00
|
|
|
piob->piob_file = file;
|
|
|
|
piob->piob_io_length = segmented_length;
|
|
|
|
}
|
2001-12-24 03:51:06 +01:00
|
|
|
else if (!GetOverlappedResult( desc,
|
|
|
|
overlapped_ptr,
|
|
|
|
&actual_length,
|
|
|
|
TRUE) ||
|
|
|
|
actual_length != segmented_length)
|
|
|
|
{
|
|
|
|
if (piob) {
|
|
|
|
piob->piob_flags = PIOB_error;
|
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
release_io_event(file, overlapped_ptr);
|
|
|
|
return nt_error("GetOverlappedResult", file, isc_io_read_err,
|
|
|
|
status_vector);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!piob || (piob->piob_flags & (PIOB_success | PIOB_error)))
|
|
|
|
release_io_event(file, overlapped_ptr);
|
|
|
|
|
|
|
|
buffer += segmented_length;
|
|
|
|
}
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
return true;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SUPERSERVER_V2
|
2004-02-20 07:43:27 +01:00
|
|
|
bool PIO_status(phys_io_blk* piob, ISC_STATUS* status_vector)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ s t a t u s
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Check the status of an asynchronous I/O.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
|
|
|
|
if (!(piob->piob_flags & PIOB_success)) {
|
|
|
|
if (piob->piob_flags & PIOB_error) {
|
2004-02-20 07:43:27 +01:00
|
|
|
return false;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
2004-03-18 06:56:06 +01:00
|
|
|
DWORD actual_length;
|
2001-05-23 15:26:42 +02:00
|
|
|
if (!GetOverlappedResult((HANDLE) piob->piob_desc,
|
|
|
|
(OVERLAPPED *) & piob->piob_io_event,
|
|
|
|
&actual_length,
|
|
|
|
piob->piob_wait) ||
|
2004-02-20 07:43:27 +01:00
|
|
|
actual_length != piob->piob_io_length)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
release_io_event(piob->piob_file,
|
|
|
|
(OVERLAPPED *) & piob->piob_io_event);
|
|
|
|
return nt_error("GetOverlappedResult", piob->piob_file,
|
|
|
|
isc_io_error, status_vector);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
release_io_event(piob->piob_file, (OVERLAPPED *) & piob->piob_io_event);
|
2004-02-20 07:43:27 +01:00
|
|
|
return true;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2004-03-20 15:57:40 +01:00
|
|
|
bool PIO_write(jrd_file* file, BufferDesc* bdb, Ods::pag* page, ISC_STATUS* status_vector)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* P I O _ w r i t e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Write a data page.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
OVERLAPPED overlapped, *overlapped_ptr;
|
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
Database* dbb = bdb->bdb_dbb;
|
2004-03-18 06:56:06 +01:00
|
|
|
const DWORD size = dbb->dbb_page_size;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2001-12-24 03:51:06 +01:00
|
|
|
file = seek_file(file, bdb, status_vector, &overlapped,
|
|
|
|
&overlapped_ptr);
|
|
|
|
if (!file) {
|
2004-02-20 07:43:27 +01:00
|
|
|
return false;
|
2001-12-24 03:51:06 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2001-12-24 03:51:06 +01:00
|
|
|
HANDLE desc = (HANDLE) ((file->fil_flags & FIL_force_write) ?
|
2001-05-23 15:26:42 +02:00
|
|
|
file->fil_force_write_desc : file->fil_desc);
|
|
|
|
|
2004-05-27 18:26:52 +02:00
|
|
|
if (dbb->dbb_encrypt_key.hasData()) {
|
2001-05-23 15:26:42 +02:00
|
|
|
SLONG spare_buffer[MAX_PAGE_SIZE / sizeof(SLONG)];
|
|
|
|
|
2004-03-14 14:40:14 +01:00
|
|
|
(*dbb->dbb_encrypt) (dbb->dbb_encrypt_key.c_str(),
|
2001-05-23 15:26:42 +02:00
|
|
|
page, size, spare_buffer);
|
|
|
|
|
2004-03-18 06:56:06 +01:00
|
|
|
DWORD actual_length;
|
2001-12-24 03:51:06 +01:00
|
|
|
if (!WriteFile(desc, spare_buffer, size, &actual_length, overlapped_ptr)
|
|
|
|
|| actual_length != size)
|
|
|
|
{
|
|
|
|
if (ostype == OS_CHICAGO) {
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2001-12-24 03:51:06 +01:00
|
|
|
}
|
2003-02-09 10:52:32 +01:00
|
|
|
return nt_error("WriteFile", file, isc_io_write_err, status_vector);
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2004-03-18 06:56:06 +01:00
|
|
|
DWORD actual_length;
|
2005-02-17 08:28:03 +01:00
|
|
|
if (!WriteFile(desc, page, size, &actual_length, overlapped_ptr)
|
|
|
|
|| actual_length != size )
|
2002-04-29 17:05:11 +02:00
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
#ifdef SUPERSERVER_V2
|
2003-02-09 10:52:32 +01:00
|
|
|
if (!GetOverlappedResult(desc, overlapped_ptr, &actual_length, TRUE)
|
2001-11-02 21:59:38 +01:00
|
|
|
|| actual_length != size)
|
2002-04-29 17:05:11 +02:00
|
|
|
{
|
|
|
|
release_io_event(file, overlapped_ptr);
|
|
|
|
return nt_error("GetOverlappedResult", file, isc_io_write_err,
|
|
|
|
status_vector);
|
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
#else
|
2002-04-29 17:05:11 +02:00
|
|
|
if (ostype == OS_CHICAGO) {
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2002-04-29 17:05:11 +02:00
|
|
|
}
|
2003-02-09 10:52:32 +01:00
|
|
|
return nt_error("WriteFile", file, isc_io_write_err, status_vector);
|
2001-05-23 15:26:42 +02:00
|
|
|
#endif
|
2002-04-29 17:05:11 +02:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef SUPERSERVER_V2
|
|
|
|
release_io_event(file, overlapped_ptr);
|
|
|
|
#endif
|
|
|
|
|
2001-12-24 03:51:06 +01:00
|
|
|
if (ostype == OS_CHICAGO) {
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2001-12-24 03:51:06 +01:00
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
return true;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-12-24 03:51:06 +01:00
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
static ULONG get_number_of_pages(const jrd_file* file, const USHORT pagesize)
|
2002-11-10 16:38:38 +01:00
|
|
|
{
|
2001-12-24 03:51:06 +01:00
|
|
|
/**************************************
|
|
|
|
*
|
2002-11-10 16:38:38 +01:00
|
|
|
* g e t _ n u m b e r _ o f _ p a g e s
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
2001-12-24 03:51:06 +01:00
|
|
|
* Compute number of pages in file, based only on file size.
|
|
|
|
*
|
|
|
|
**************************************/
|
2002-06-04 14:03:13 +02:00
|
|
|
HANDLE hFile = (HANDLE) ((file->fil_flags & FIL_force_write) ?
|
|
|
|
file->fil_force_write_desc : file->fil_desc);
|
2001-12-24 03:51:06 +01:00
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
DWORD dwFileSizeHigh;
|
|
|
|
const DWORD dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
|
2001-12-24 03:51:06 +01:00
|
|
|
|
2003-08-09 21:01:02 +02:00
|
|
|
if (dwFileSizeLow == (DWORD) -1) {
|
2001-12-24 03:51:06 +01:00
|
|
|
nt_error("GetFileSize", file, isc_io_access_err, 0);
|
|
|
|
}
|
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
const ULONGLONG ullFileSize = (((ULONGLONG) dwFileSizeHigh) << 32)
|
|
|
|
+ dwFileSizeLow;
|
2001-12-24 03:51:06 +01:00
|
|
|
return (ULONG) ((ullFileSize + pagesize - 1) / pagesize);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2001-05-23 15:26:42 +02:00
|
|
|
#ifdef SUPERSERVER_V2
|
2004-02-20 07:43:27 +01:00
|
|
|
static void release_io_event(jrd_file* file, OVERLAPPED* overlapped)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* r e l e a s e _ i o _ e v e n t
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Release an overlapped I/O event
|
|
|
|
* back to the file block.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
if (!overlapped || !overlapped->hEvent)
|
|
|
|
return;
|
|
|
|
|
|
|
|
THD_MUTEX_LOCK(file->fil_mutex);
|
2003-10-29 11:53:47 +01:00
|
|
|
for (int i = 0; i < MAX_FILE_IO; i++)
|
2001-05-23 15:26:42 +02:00
|
|
|
if (!file->fil_io_events[i]) {
|
|
|
|
file->fil_io_events[i] = overlapped->hEvent;
|
|
|
|
overlapped->hEvent = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
|
|
|
|
|
|
|
if (overlapped->hEvent)
|
|
|
|
CloseHandle(overlapped->hEvent);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
static jrd_file* seek_file(jrd_file* file,
|
2004-03-18 06:56:06 +01:00
|
|
|
BufferDesc* bdb,
|
2003-04-10 08:49:16 +02:00
|
|
|
ISC_STATUS* status_vector,
|
2001-05-23 15:26:42 +02:00
|
|
|
OVERLAPPED* overlapped,
|
|
|
|
OVERLAPPED** overlapped_ptr)
|
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* s e e k _ f i l e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Given a buffer descriptor block, find the appropropriate
|
|
|
|
* file block and seek to the proper page in that file.
|
|
|
|
*
|
|
|
|
**************************************/
|
2004-03-07 08:58:55 +01:00
|
|
|
Database* dbb = bdb->bdb_dbb;
|
2003-10-29 11:53:47 +01:00
|
|
|
ULONG page = bdb->bdb_page;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
for (;; file = file->fil_next) {
|
|
|
|
if (!file) {
|
2003-09-26 13:46:03 +02:00
|
|
|
CORRUPT(158); /* msg 158 database file not available */
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
else if (page >= file->fil_min_page && page <= file->fil_max_page) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
page -= file->fil_min_page - file->fil_fudge;
|
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
LARGE_INTEGER liOffset;
|
2001-05-23 15:26:42 +02:00
|
|
|
liOffset.QuadPart =
|
|
|
|
UInt32x32To64((DWORD) page, (DWORD) dbb->dbb_page_size);
|
|
|
|
|
|
|
|
if (ostype == OS_CHICAGO) {
|
|
|
|
THD_MUTEX_LOCK(file->fil_mutex);
|
2003-10-29 11:53:47 +01:00
|
|
|
HANDLE desc = (HANDLE) ((file->fil_flags & FIL_force_write) ?
|
2001-05-23 15:26:42 +02:00
|
|
|
file->fil_force_write_desc : file->fil_desc);
|
|
|
|
|
|
|
|
if (SetFilePointer(desc,
|
|
|
|
(LONG) liOffset.LowPart,
|
2004-02-20 07:43:27 +01:00
|
|
|
&liOffset.HighPart, FILE_BEGIN) == 0xffffffff)
|
|
|
|
{
|
2001-05-23 15:26:42 +02:00
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
2004-02-20 07:43:27 +01:00
|
|
|
nt_error("SetFilePointer", file, isc_io_access_err, status_vector);
|
|
|
|
return 0;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
*overlapped_ptr = NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
overlapped->Offset = liOffset.LowPart;
|
|
|
|
overlapped->OffsetHigh = liOffset.HighPart;
|
|
|
|
overlapped->Internal = 0;
|
|
|
|
overlapped->InternalHigh = 0;
|
|
|
|
overlapped->hEvent = (HANDLE) 0;
|
|
|
|
|
|
|
|
*overlapped_ptr = overlapped;
|
|
|
|
|
|
|
|
#ifdef SUPERSERVER_V2
|
|
|
|
THD_MUTEX_LOCK(file->fil_mutex);
|
2001-12-24 03:51:06 +01:00
|
|
|
for (USHORT i = 0; i < MAX_FILE_IO; i++) {
|
2001-05-23 15:26:42 +02:00
|
|
|
if (overlapped->hEvent = (HANDLE) file->fil_io_events[i]) {
|
|
|
|
file->fil_io_events[i] = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
THD_MUTEX_UNLOCK(file->fil_mutex);
|
|
|
|
if (!overlapped->hEvent &&
|
2004-02-20 07:43:27 +01:00
|
|
|
!(overlapped->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
|
|
|
|
{
|
|
|
|
nt_error("CreateEvent", file, isc_io_access_err, status_vector);
|
|
|
|
return 0;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
ResetEvent(overlapped->hEvent);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-07 08:58:55 +01:00
|
|
|
static jrd_file* setup_file(Database* dbb,
|
2003-10-29 11:53:47 +01:00
|
|
|
const TEXT* file_name,
|
2001-05-23 15:26:42 +02:00
|
|
|
USHORT file_length,
|
|
|
|
HANDLE desc)
|
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* s e t u p _ f i l e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Set up file and lock blocks for a file.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
|
|
|
|
/* Allocate file block and copy file name string */
|
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
jrd_file* file = FB_NEW_RPT(*dbb->dbb_permanent, file_length + 1) jrd_file;
|
2001-12-24 03:51:06 +01:00
|
|
|
file->fil_desc = reinterpret_cast<SLONG>(desc);
|
2001-05-23 15:26:42 +02:00
|
|
|
file->fil_force_write_desc =
|
2001-12-24 03:51:06 +01:00
|
|
|
reinterpret_cast<SLONG>(INVALID_HANDLE_VALUE);
|
2001-05-23 15:26:42 +02:00
|
|
|
file->fil_length = file_length;
|
2003-08-09 21:01:02 +02:00
|
|
|
file->fil_max_page = (ULONG) -1;
|
2002-04-29 17:05:11 +02:00
|
|
|
#ifdef SUPERSERVER_V2
|
|
|
|
memset(file->fil_io_events, 0, MAX_FILE_IO * sizeof(SLONG));
|
|
|
|
#endif
|
2001-05-23 15:26:42 +02:00
|
|
|
MOVE_FAST(file_name, file->fil_string, file_length);
|
|
|
|
file->fil_string[file_length] = 0;
|
|
|
|
|
|
|
|
/* If this isn't the primary file, we're done */
|
|
|
|
|
|
|
|
if (dbb->dbb_file)
|
|
|
|
return file;
|
|
|
|
|
|
|
|
/* Set a local variable that indicates whether we're running
|
|
|
|
under Windows/NT or Chicago */
|
2003-10-29 11:53:47 +01:00
|
|
|
// CVC: local variable to all this unit, it means.
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2001-12-24 03:51:06 +01:00
|
|
|
ostype = ISC_is_WinNT() ? OS_WINDOWS_NT : OS_CHICAGO;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
|
|
|
/* Build unique lock string for file and construct lock block */
|
|
|
|
|
2004-03-18 06:56:06 +01:00
|
|
|
BY_HANDLE_FILE_INFORMATION file_info;
|
2001-05-23 15:26:42 +02:00
|
|
|
GetFileInformationByHandle((HANDLE) desc, &file_info);
|
2004-03-18 06:56:06 +01:00
|
|
|
UCHAR lock_string[32];
|
2003-10-29 11:53:47 +01:00
|
|
|
UCHAR* p = lock_string;
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
const UCHAR* q = (UCHAR *) &file_info.dwVolumeSerialNumber;
|
|
|
|
size_t l = sizeof(file_info.dwVolumeSerialNumber);
|
|
|
|
do {
|
2001-05-23 15:26:42 +02:00
|
|
|
*p++ = *q++;
|
2003-10-29 11:53:47 +01:00
|
|
|
} while (--l);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
q = (UCHAR *) &file_info.nFileIndexHigh;
|
2001-05-23 15:26:42 +02:00
|
|
|
l = sizeof(file_info.nFileIndexHigh);
|
2003-10-29 11:53:47 +01:00
|
|
|
do {
|
2001-05-23 15:26:42 +02:00
|
|
|
*p++ = *q++;
|
2003-10-29 11:53:47 +01:00
|
|
|
} while (--l);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
q = (UCHAR *) &file_info.nFileIndexLow;
|
2001-05-23 15:26:42 +02:00
|
|
|
l = sizeof(file_info.nFileIndexLow);
|
2003-10-29 11:53:47 +01:00
|
|
|
do {
|
2001-05-23 15:26:42 +02:00
|
|
|
*p++ = *q++;
|
2003-10-29 11:53:47 +01:00
|
|
|
} while (--l);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2003-10-29 11:53:47 +01:00
|
|
|
// We know p only was incremented, so can use safely size_t instead of ptrdiff_t
|
2001-05-23 15:26:42 +02:00
|
|
|
l = p - lock_string;
|
|
|
|
|
2004-03-18 06:56:06 +01:00
|
|
|
Lock* lock = FB_NEW_RPT(*dbb->dbb_permanent, l) Lock;
|
2004-02-20 07:43:27 +01:00
|
|
|
dbb->dbb_lock = lock;
|
2001-05-23 15:26:42 +02:00
|
|
|
lock->lck_type = LCK_database;
|
2003-09-01 13:08:30 +02:00
|
|
|
lock->lck_owner_handle = LCK_get_owner_handle(NULL, lock->lck_type);
|
2001-12-24 03:51:06 +01:00
|
|
|
lock->lck_object = reinterpret_cast<blk*>(dbb);
|
2001-05-23 15:26:42 +02:00
|
|
|
lock->lck_length = l;
|
|
|
|
lock->lck_dbb = dbb;
|
2003-05-16 22:35:19 +02:00
|
|
|
lock->lck_ast = CCH_down_grade_dbb;
|
2001-05-23 15:26:42 +02:00
|
|
|
MOVE_FAST(lock_string, lock->lck_key.lck_string, l);
|
|
|
|
|
|
|
|
/* Try to get an exclusive lock on database. If this fails, insist
|
|
|
|
on at least a shared lock */
|
|
|
|
|
|
|
|
dbb->dbb_flags |= DBB_exclusive;
|
2003-09-01 13:08:30 +02:00
|
|
|
if (!LCK_lock(NULL, lock, LCK_EX, LCK_NO_WAIT)) {
|
2001-05-23 15:26:42 +02:00
|
|
|
dbb->dbb_flags &= ~DBB_exclusive;
|
2004-05-22 16:28:54 +02:00
|
|
|
thread_db* tdbb = JRD_get_thread_data();
|
2004-02-25 07:33:26 +01:00
|
|
|
|
|
|
|
while (!LCK_lock(tdbb, lock, LCK_SW, -1)) {
|
|
|
|
tdbb->tdbb_status_vector[0] = 0; // Clean status vector from lock manager error code
|
|
|
|
// If we are in a single-threaded maintenance mode then clean up and stop waiting
|
|
|
|
SCHAR spare_memory[MIN_PAGE_SIZE*2];
|
|
|
|
SCHAR *header_page_buffer = (SCHAR*) FB_ALIGN((IPTR)spare_memory, MIN_PAGE_SIZE);
|
|
|
|
|
|
|
|
try {
|
|
|
|
dbb->dbb_file = file;
|
|
|
|
PIO_header(dbb, header_page_buffer, MIN_PAGE_SIZE);
|
2004-05-01 00:47:16 +02:00
|
|
|
if ((reinterpret_cast<Ods::header_page*>(header_page_buffer)->hdr_flags & Ods::hdr_shutdown_mask) == Ods::hdr_shutdown_single)
|
2004-02-25 07:33:26 +01:00
|
|
|
ERR_post(isc_shutdown, isc_arg_string, ERR_string(file_name, file_length), 0);
|
|
|
|
dbb->dbb_file = NULL; // Will be set again later by the caller
|
|
|
|
} catch(const std::exception&) {
|
|
|
|
delete dbb->dbb_lock;
|
|
|
|
dbb->dbb_lock = NULL;
|
|
|
|
delete file;
|
|
|
|
dbb->dbb_file = NULL; // Will be set again later by the caller
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2001-12-24 03:51:06 +01:00
|
|
|
static bool MaybeCloseFile(SLONG* pFile)
|
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* M a y b e C l o s e F i l e
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* If the file is open, close it.
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
|
|
|
|
if (pFile && (HANDLE)*pFile != INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
CloseHandle((HANDLE)*pFile);
|
|
|
|
*pFile = (SLONG) INVALID_HANDLE_VALUE;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
static bool nt_error(TEXT* string,
|
|
|
|
const jrd_file* file,
|
2003-04-10 08:49:16 +02:00
|
|
|
ISC_STATUS operation,
|
|
|
|
ISC_STATUS* status_vector)
|
2001-05-23 15:26:42 +02:00
|
|
|
{
|
|
|
|
/**************************************
|
|
|
|
*
|
|
|
|
* n t _ e r r o r
|
|
|
|
*
|
|
|
|
**************************************
|
|
|
|
*
|
|
|
|
* Functional description
|
|
|
|
* Somebody has noticed a file system error and expects error
|
|
|
|
* to do something about it. Harumph!
|
|
|
|
*
|
|
|
|
**************************************/
|
|
|
|
|
|
|
|
if (status_vector) {
|
|
|
|
*status_vector++ = isc_arg_gds;
|
|
|
|
*status_vector++ = isc_io_error;
|
2003-11-11 13:19:20 +01:00
|
|
|
*status_vector++ = isc_arg_string;
|
2003-04-10 08:49:16 +02:00
|
|
|
*status_vector++ = (ISC_STATUS) string;
|
2003-11-11 13:19:20 +01:00
|
|
|
*status_vector++ = isc_arg_string;
|
2001-05-23 15:26:42 +02:00
|
|
|
*status_vector++ =
|
2004-02-20 07:43:27 +01:00
|
|
|
(ISC_STATUS)(U_IPTR) ERR_string(file->fil_string, file->fil_length);
|
2001-05-23 15:26:42 +02:00
|
|
|
*status_vector++ = isc_arg_gds;
|
|
|
|
*status_vector++ = operation;
|
2003-11-11 13:19:20 +01:00
|
|
|
*status_vector++ = isc_arg_win32;
|
2001-05-23 15:26:42 +02:00
|
|
|
*status_vector++ = GetLastError();
|
2003-11-11 13:19:20 +01:00
|
|
|
*status_vector++ = isc_arg_end;
|
2004-02-20 07:43:27 +01:00
|
|
|
return false;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ERR_post(isc_io_error,
|
2003-11-11 13:19:20 +01:00
|
|
|
isc_arg_string, string,
|
|
|
|
isc_arg_string, ERR_string(file->fil_string, file->fil_length),
|
|
|
|
isc_arg_gds, operation, isc_arg_win32, GetLastError(), 0);
|
2001-05-23 15:26:42 +02:00
|
|
|
|
2004-02-20 07:43:27 +01:00
|
|
|
return true;
|
2001-05-23 15:26:42 +02:00
|
|
|
}
|
2003-10-29 11:53:47 +01:00
|
|
|
|