8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-25 04:43:03 +01:00
firebird-mirror/src/jrd/shut.cpp

621 lines
15 KiB
C++
Raw Normal View History

2001-05-23 15:26:42 +02:00
/*
* PROGRAM: JRD Access Method
2003-12-31 06:36:12 +01:00
* MODULE: shut.cpp
2001-05-23 15:26:42 +02:00
* DESCRIPTION: Database shutdown handler
*
* 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): ______________________________________.
*/
#include "firebird.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"
#include "../jrd/scl.h"
2003-11-08 17:40:17 +01:00
#include "../jrd/ibase.h"
#include "../jrd/nbak.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/ods.h"
#include "../jrd/cch_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/rlck_proto.h"
#include "../jrd/shut_proto.h"
#include "../jrd/thread_proto.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/tra_proto.h"
#include "../jrd/extds/ExtDS.h"
2001-05-23 15:26:42 +02:00
using namespace Jrd;
using namespace Firebird;
2004-05-07 00:11:24 +02:00
const SSHORT SHUT_WAIT_TIME = 5;
2001-05-23 15:26:42 +02:00
// Shutdown lock data
2008-12-13 10:26:00 +01:00
union shutdown_data
{
struct {
SSHORT flag;
SSHORT delay;
} data_items;
SLONG data_long;
};
2008-12-05 02:20:14 +01:00
// Define this to true if you need to allow no-op behavior when requested shutdown mode
// matches current. Logic of jrd8_create_database may need attention in this case too
2004-05-07 00:11:24 +02:00
const bool IGNORE_SAME_MODE = false;
static void bad_mode();
static void same_mode();
2007-03-09 09:27:34 +01:00
static void check_backup_state(thread_db*);
static bool notify_shutdown(thread_db*, SSHORT, SSHORT);
static bool shutdown_locks(thread_db*, SSHORT);
2001-05-23 15:26:42 +02:00
bool SHUT_blocking_ast(thread_db* tdbb)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* S H U T _ b l o c k i n g _ a s t
*
**************************************
*
* Functional description
* Read data from database lock for
* shutdown instructions.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
shutdown_data data;
data.data_long = LCK_read_data(tdbb, dbb->dbb_lock);
const SSHORT flag = data.data_items.flag;
const SSHORT delay = data.data_items.delay;
2001-05-23 15:26:42 +02:00
2009-11-23 10:13:38 +01:00
// Database shutdown has been cancelled.
2001-05-23 15:26:42 +02:00
// Delay of -1 means we're going online
2009-06-27 08:23:36 +02:00
if (delay == -1)
{
2001-05-23 15:26:42 +02:00
dbb->dbb_ast_flags &=
~(DBB_shut_attach | DBB_shut_tran | DBB_shut_force |
DBB_shutdown | DBB_shutdown_single | DBB_shutdown_full);
2008-12-13 10:26:00 +01:00
switch (flag & isc_dpb_shut_mode_mask)
{
case isc_dpb_shut_normal:
break;
case isc_dpb_shut_multi:
dbb->dbb_ast_flags |= DBB_shutdown;
break;
case isc_dpb_shut_single:
dbb->dbb_ast_flags |= DBB_shutdown | DBB_shutdown_single;
break;
case isc_dpb_shut_full:
dbb->dbb_ast_flags |= DBB_shutdown | DBB_shutdown_full;
break;
default:
fb_assert(false);
}
2008-12-05 02:20:14 +01:00
return false;
2001-05-23 15:26:42 +02:00
}
if ((flag & isc_dpb_shut_force) && !delay)
return shutdown_locks(tdbb, flag);
2008-01-16 10:48:41 +01:00
if (flag & isc_dpb_shut_attachment)
dbb->dbb_ast_flags |= DBB_shut_attach;
if (flag & isc_dpb_shut_force)
dbb->dbb_ast_flags |= DBB_shut_force;
if (flag & isc_dpb_shut_transaction)
dbb->dbb_ast_flags |= DBB_shut_tran;
2008-01-16 10:48:41 +01:00
return false;
}
2001-05-23 15:26:42 +02:00
void SHUT_database(thread_db* tdbb, SSHORT flag, SSHORT delay)
{
/**************************************
*
* S H U T _ d a t a b a s e
*
**************************************
*
* Functional description
* Schedule database for shutdown
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
Jrd::Attachment* attachment = tdbb->getAttachment();
2001-05-23 15:26:42 +02:00
2009-11-23 10:13:38 +01:00
// Only platform's user locksmith can shutdown or bring online a database.
2008-12-05 02:20:14 +01:00
if (!attachment->locksmith())
{
ERR_post(Arg::Gds(isc_no_priv) << "shutdown" << "database" << dbb->dbb_filename);
}
const int shut_mode = flag & isc_dpb_shut_mode_mask;
// Check if requested shutdown mode is valid
// Note that if we are already in requested mode we just return true.
2008-12-05 02:20:14 +01:00
// This is required to ensure backward compatible behavior (gbak relies on that,
// user-written scripts may rely on this behaviour too)
2009-01-20 09:33:59 +01:00
switch (shut_mode)
{
case isc_dpb_shut_full:
2008-12-05 02:20:14 +01:00
if (dbb->dbb_ast_flags & DBB_shutdown_full)
{
same_mode();
return;
}
break;
case isc_dpb_shut_multi:
2008-12-13 10:26:00 +01:00
if ((dbb->dbb_ast_flags & DBB_shutdown_full) || (dbb->dbb_ast_flags & DBB_shutdown_single))
{
bad_mode();
}
if (dbb->dbb_ast_flags & DBB_shutdown)
{
same_mode();
return;
}
break;
case isc_dpb_shut_single:
if (dbb->dbb_ast_flags & DBB_shutdown_full)
{
bad_mode();
}
if (dbb->dbb_ast_flags & DBB_shutdown_single)
{
same_mode();
return;
}
break;
2008-12-05 02:20:14 +01:00
case isc_dpb_shut_normal:
if (!(dbb->dbb_ast_flags & DBB_shutdown))
{
same_mode();
return;
}
bad_mode();
default:
bad_mode(); // unexpected mode
2001-05-23 15:26:42 +02:00
}
2008-12-05 02:20:14 +01:00
2007-03-09 09:27:34 +01:00
// Reject exclusive and single-user shutdown attempts
// for a physically locked database
2008-12-13 10:26:00 +01:00
if (shut_mode == isc_dpb_shut_full || shut_mode == isc_dpb_shut_single)
2007-03-09 09:27:34 +01:00
{
check_backup_state(tdbb);
}
2001-05-23 15:26:42 +02:00
attachment->att_flags |= ATT_shutdown_manager;
--dbb->dbb_use_count;
2009-11-23 10:13:38 +01:00
// Database is being shutdown. First notification gives shutdown type and delay in seconds.
2001-05-23 15:26:42 +02:00
bool exclusive = notify_shutdown(tdbb, flag, delay);
2001-05-23 15:26:42 +02:00
2009-11-23 10:13:38 +01:00
// Notify local attachments
2001-05-23 15:26:42 +02:00
SHUT_blocking_ast(tdbb);
2001-05-23 15:26:42 +02:00
2009-11-23 10:13:38 +01:00
// Try to get exclusive database lock periodically up to specified delay. If we
// haven't gotten it report shutdown error for weaker forms. For forced shutdown
// keep notifying until successful.
2001-05-23 15:26:42 +02:00
SSHORT timeout = delay - SHUT_WAIT_TIME;
if (!exclusive)
2001-12-24 03:51:06 +01:00
{
for (; timeout >= 0; timeout -= SHUT_WAIT_TIME)
2001-12-24 03:51:06 +01:00
{
if ((exclusive = notify_shutdown(tdbb, flag, timeout)) ||
2008-12-13 10:26:00 +01:00
!(dbb->dbb_ast_flags & (DBB_shut_attach | DBB_shut_tran | DBB_shut_force)))
{
break;
}
2001-12-24 03:51:06 +01:00
}
}
2001-05-23 15:26:42 +02:00
2008-12-13 10:26:00 +01:00
if (!exclusive && (timeout > 0 || flag & (isc_dpb_shut_attachment | isc_dpb_shut_transaction)))
2001-12-24 03:51:06 +01:00
{
2009-11-23 10:13:38 +01:00
notify_shutdown(tdbb, 0, 0); // Tell everyone we're giving up
SHUT_blocking_ast(tdbb);
2001-05-23 15:26:42 +02:00
attachment->att_flags &= ~ATT_shutdown_manager;
++dbb->dbb_use_count;
ERR_post(Arg::Gds(isc_shutfail));
2001-05-23 15:26:42 +02:00
}
2009-11-23 10:13:38 +01:00
// Once there are no more transactions active, force all remaining attachments to shutdown.
2001-05-23 15:26:42 +02:00
2009-06-27 08:23:36 +02:00
if (flag & isc_dpb_shut_transaction)
{
exclusive = false;
flag = isc_dpb_shut_force | shut_mode;
2001-05-23 15:26:42 +02:00
}
dbb->dbb_ast_flags |= DBB_shutdown;
dbb->dbb_ast_flags &= ~(DBB_shutdown_single | DBB_shutdown_full);
2009-01-20 09:33:59 +01:00
switch (shut_mode)
{
case isc_dpb_shut_normal:
case isc_dpb_shut_multi:
break;
case isc_dpb_shut_single:
dbb->dbb_ast_flags |= DBB_shutdown_single;
break;
case isc_dpb_shut_full:
dbb->dbb_ast_flags |= DBB_shutdown_full;
break;
default:
fb_assert(false);
}
2001-05-23 15:26:42 +02:00
2009-06-27 08:23:36 +02:00
if (!exclusive && (flag & isc_dpb_shut_force))
{
2001-12-24 03:51:06 +01:00
// TMN: Ugly counting!
2008-12-23 09:41:23 +01:00
while (!notify_shutdown(tdbb, flag, 0))
;
2001-12-24 03:51:06 +01:00
}
2001-05-23 15:26:42 +02:00
++dbb->dbb_use_count;
dbb->dbb_ast_flags &= ~(DBB_shut_force | DBB_shut_attach | DBB_shut_tran);
2006-05-22 00:07:35 +02:00
WIN window(HEADER_PAGE_NUMBER);
2008-12-13 10:26:00 +01:00
Ods::header_page* header = (Ods::header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
2001-05-23 15:26:42 +02:00
CCH_MARK_MUST_WRITE(tdbb, &window);
// Set appropriate shutdown mode in database header
header->hdr_flags &= ~Ods::hdr_shutdown_mask;
2009-01-20 09:33:59 +01:00
switch (shut_mode)
{
case isc_dpb_shut_normal:
break;
case isc_dpb_shut_multi:
header->hdr_flags |= Ods::hdr_shutdown_multi;
break;
case isc_dpb_shut_single:
header->hdr_flags |= Ods::hdr_shutdown_single;
break;
case isc_dpb_shut_full:
header->hdr_flags |= Ods::hdr_shutdown_full;
break;
default:
fb_assert(false);
}
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
CCH_release_exclusive(tdbb);
}
void SHUT_init(thread_db* tdbb)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* S H U T _ i n i t
*
**************************************
*
* Functional description
* Read data from database lock for
* shutdown instructions.
*
**************************************/
SHUT_blocking_ast(tdbb);
2001-05-23 15:26:42 +02:00
}
void SHUT_online(thread_db* tdbb, SSHORT flag)
2004-03-07 08:58:55 +01:00
{
/**************************************
*
* S H U T _ o n l i n e
*
**************************************
*
* Functional description
* Move database to "more online" state
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
Jrd::Attachment* attachment = tdbb->getAttachment();
2004-03-07 08:58:55 +01:00
2009-11-23 10:13:38 +01:00
// Only platform's user locksmith can shutdown or bring online a database.
2004-03-07 08:58:55 +01:00
2009-01-20 09:33:59 +01:00
if (!attachment->att_user->locksmith())
{
ERR_post(Arg::Gds(isc_no_priv) << "bring online" << "database" << dbb->dbb_filename);
2004-03-07 08:58:55 +01:00
}
2008-12-05 02:20:14 +01:00
const int shut_mode = flag & isc_dpb_shut_mode_mask;
2004-03-07 08:58:55 +01:00
// Check if requested shutdown mode is valid
2009-01-20 09:33:59 +01:00
switch (shut_mode)
{
2004-03-07 08:58:55 +01:00
case isc_dpb_shut_normal:
2008-12-05 02:20:14 +01:00
if (!(dbb->dbb_ast_flags & DBB_shutdown))
{
same_mode(); // normal -> normal
return;
}
2004-03-07 08:58:55 +01:00
break;
case isc_dpb_shut_multi:
if (!(dbb->dbb_ast_flags & DBB_shutdown))
{
bad_mode(); // normal -> multi
}
2008-12-13 10:26:00 +01:00
if (!(dbb->dbb_ast_flags & DBB_shutdown_full) && !(dbb->dbb_ast_flags & DBB_shutdown_single))
2004-03-07 08:58:55 +01:00
{
same_mode(); // multi -> multi
return;
2004-03-07 08:58:55 +01:00
}
break;
2008-12-05 02:20:14 +01:00
case isc_dpb_shut_single:
2004-03-07 08:58:55 +01:00
if (dbb->dbb_ast_flags & DBB_shutdown_single)
{
same_mode(); //single -> single
return;
}
2004-03-07 08:58:55 +01:00
if (!(dbb->dbb_ast_flags & DBB_shutdown_full))
{
bad_mode(); // !full -> single
}
2008-12-05 02:20:14 +01:00
break;
2004-03-07 08:58:55 +01:00
case isc_dpb_shut_full:
if (dbb->dbb_ast_flags & DBB_shutdown_full)
{
same_mode(); // full -> full
return;
2004-03-07 08:58:55 +01:00
}
bad_mode();
2004-03-07 08:58:55 +01:00
default: // isc_dpb_shut_full
bad_mode(); // unexpected mode
2004-03-07 08:58:55 +01:00
}
2008-12-05 02:20:14 +01:00
2007-03-09 09:27:34 +01:00
// Reject exclusive and single-user shutdown attempts
// for a physically locked database
2008-12-13 10:26:00 +01:00
if (shut_mode == isc_dpb_shut_full || shut_mode == isc_dpb_shut_single)
2007-03-09 09:27:34 +01:00
{
check_backup_state(tdbb);
}
2009-11-23 10:13:38 +01:00
// Clear shutdown flag on database header page
2004-03-07 08:58:55 +01:00
2006-05-22 00:07:35 +02:00
WIN window(HEADER_PAGE_NUMBER);
Ods::header_page* header = (Ods::header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
2004-03-07 08:58:55 +01:00
CCH_MARK_MUST_WRITE(tdbb, &window);
// Set appropriate shutdown mode in database header
header->hdr_flags &= ~Ods::hdr_shutdown_mask;
2009-01-20 09:33:59 +01:00
switch (shut_mode)
{
2004-03-07 08:58:55 +01:00
case isc_dpb_shut_normal:
break;
case isc_dpb_shut_multi:
header->hdr_flags |= Ods::hdr_shutdown_multi;
2004-03-07 08:58:55 +01:00
break;
case isc_dpb_shut_single:
header->hdr_flags |= Ods::hdr_shutdown_single;
2004-03-07 08:58:55 +01:00
break;
case isc_dpb_shut_full:
header->hdr_flags |= Ods::hdr_shutdown_full;
2004-03-07 08:58:55 +01:00
break;
default:
fb_assert(false);
2004-03-07 08:58:55 +01:00
}
CCH_RELEASE(tdbb, &window);
2009-11-23 10:13:38 +01:00
// Notify existing database clients that a currently scheduled shutdown is cancelled.
2004-03-07 08:58:55 +01:00
if (notify_shutdown(tdbb, shut_mode, -1))
2004-03-07 08:58:55 +01:00
CCH_release_exclusive(tdbb);
2009-11-23 10:13:38 +01:00
// Notify local attachments
2004-03-07 08:58:55 +01:00
SHUT_blocking_ast(tdbb);
}
2004-03-07 08:58:55 +01:00
2008-12-05 02:20:14 +01:00
static void same_mode()
{
2009-01-20 09:33:59 +01:00
if (!IGNORE_SAME_MODE)
{
bad_mode();
}
2004-03-07 08:58:55 +01:00
}
static void bad_mode()
2007-03-09 09:27:34 +01:00
{
Database* dbb = JRD_get_thread_data()->getDatabase();
ERR_post(Arg::Gds(isc_bad_shutdown_mode) << Arg::Str(dbb->dbb_database_name));
2004-03-07 08:58:55 +01:00
}
2007-03-09 09:27:34 +01:00
static void check_backup_state(thread_db* tdbb)
{
Database* dbb = tdbb->getDatabase();
2007-03-09 09:27:34 +01:00
BackupManager::StateReadGuard stateGuard(tdbb);
2007-03-09 09:27:34 +01:00
if (dbb->dbb_backup_manager->getState() != nbak_state_normal)
{
ERR_post(Arg::Gds(isc_bad_shutdown_mode) << Arg::Str(dbb->dbb_filename));
2007-03-09 09:27:34 +01:00
}
}
static bool notify_shutdown(thread_db* tdbb, SSHORT flag, SSHORT delay)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* n o t i f y _ s h u t d o w n
*
**************************************
*
* Functional description
* Notify database users that shutdown
* status of a database is changing.
* Pulse database lock and pass shutdown
* flags and delay via lock data.
*
**************************************/
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
shutdown_data data;
2001-05-23 15:26:42 +02:00
data.data_items.flag = flag;
data.data_items.delay = delay;
LCK_write_data(tdbb, dbb->dbb_lock, data.data_long);
2001-05-23 15:26:42 +02:00
2009-11-23 10:13:38 +01:00
// Send blocking ASTs to database users
2001-05-23 15:26:42 +02:00
2008-12-23 09:41:23 +01:00
const bool exclusive = CCH_exclusive(tdbb, LCK_PW, delay > 0 ? -SHUT_WAIT_TIME : -1);
if (exclusive && (delay != -1)) {
return shutdown_locks(tdbb, flag);
2001-12-24 03:51:06 +01:00
}
2003-11-08 17:40:17 +01:00
if ((flag & isc_dpb_shut_force) && !delay) {
return shutdown_locks(tdbb, flag);
2001-12-24 03:51:06 +01:00
}
2008-12-23 09:41:23 +01:00
if ((flag & isc_dpb_shut_transaction) && !TRA_active_transactions(tdbb, dbb))
2001-12-24 03:51:06 +01:00
{
return true;
2001-12-24 03:51:06 +01:00
}
return exclusive;
2001-05-23 15:26:42 +02:00
}
static bool shutdown_locks(thread_db* tdbb, SSHORT flag)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* s h u t d o w n _ l o c k s
*
**************************************
*
* Functional description
* Release all attachment and database
* locks if database is quiet.
*
**************************************/
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
2009-11-23 10:13:38 +01:00
// Mark database and all active attachments as shutdown.
2001-05-23 15:26:42 +02:00
dbb->dbb_ast_flags &= ~(DBB_shutdown | DBB_shutdown_single | DBB_shutdown_full);
2008-12-05 02:20:14 +01:00
2009-01-20 09:33:59 +01:00
switch (flag & isc_dpb_shut_mode_mask)
{
case isc_dpb_shut_normal:
break;
case isc_dpb_shut_multi:
dbb->dbb_ast_flags |= DBB_shutdown;
break;
case isc_dpb_shut_single:
dbb->dbb_ast_flags |= DBB_shutdown | DBB_shutdown_single;
break;
case isc_dpb_shut_full:
dbb->dbb_ast_flags |= DBB_shutdown | DBB_shutdown_full;
break;
default:
fb_assert(false);
}
2001-05-23 15:26:42 +02:00
Jrd::Attachment* attachment;
2008-12-05 02:20:14 +01:00
2008-12-13 10:26:00 +01:00
for (attachment = dbb->dbb_attachments; attachment; attachment = attachment->att_next)
2003-09-13 14:03:11 +02:00
{
if (!(attachment->att_flags & ATT_shutdown_manager))
{
2003-09-13 14:03:11 +02:00
attachment->att_flags |= ATT_shutdown;
attachment->cancelExternalConnection(tdbb);
}
2003-09-13 14:03:11 +02:00
}
2001-05-23 15:26:42 +02:00
JRD_shutdown_attachments(dbb);
for (int retry = 0; retry < 10 && dbb->dbb_use_count; retry++)
2009-06-27 08:23:36 +02:00
{
// Let active database threads rundown
Database::Checkout dcoHolder(dbb);
THREAD_SLEEP(1 * 100);
}
if (dbb->dbb_use_count)
{
return false;
2001-05-23 15:26:42 +02:00
}
2009-11-23 10:13:38 +01:00
// Since no attachment is actively running, release all
// attachment-specfic locks while they're not looking.
2001-05-23 15:26:42 +02:00
const Jrd::Attachment* shut_attachment = NULL;
2001-05-23 15:26:42 +02:00
2008-12-13 10:26:00 +01:00
for (attachment = dbb->dbb_attachments; attachment; attachment = attachment->att_next)
{
2009-06-27 08:23:36 +02:00
if (attachment->att_flags & ATT_shutdown_manager)
{
2001-05-23 15:26:42 +02:00
shut_attachment = attachment;
continue;
}
if (attachment->att_id_lock)
LCK_release(tdbb, attachment->att_id_lock);
TRA_shutdown_attachment(tdbb, attachment);
}
2009-11-23 10:13:38 +01:00
// Release database locks that are shared by all attachments.
// These include relation and index existence locks, as well
// as, relation interest and record locking locks for PC semantic
// record locking.
2001-05-23 15:26:42 +02:00
CMP_shutdown_database(tdbb);
2009-11-23 10:13:38 +01:00
// If shutdown manager is here, leave enough database lock context
// to run as a normal attachment. Otherwise, get rid of the rest
// of the database locks.
2001-05-23 15:26:42 +02:00
2009-06-27 08:23:36 +02:00
if (!shut_attachment)
{
2001-05-23 15:26:42 +02:00
CCH_shutdown_database(dbb);
if (dbb->dbb_monitor_lock)
LCK_release(tdbb, dbb->dbb_monitor_lock);
2001-05-23 15:26:42 +02:00
if (dbb->dbb_shadow_lock)
LCK_release(tdbb, dbb->dbb_shadow_lock);
if (dbb->dbb_retaining_lock)
LCK_release(tdbb, dbb->dbb_retaining_lock);
if (dbb->dbb_sh_counter_lock)
LCK_release(tdbb, dbb->dbb_sh_counter_lock);
2001-05-23 15:26:42 +02:00
if (dbb->dbb_lock)
LCK_release(tdbb, dbb->dbb_lock);
dbb->dbb_backup_manager->shutdown(tdbb);
2001-05-23 15:26:42 +02:00
dbb->dbb_ast_flags |= DBB_shutdown_locks;
}
return true;
2001-05-23 15:26:42 +02:00
}