2009-10-25 18:12:12 +03:00
/*
Unix SMB / CIFS implementation .
global locks based on dbwrap and messaging
Copyright ( C ) 2009 by Volker Lendecke
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "includes.h"
2011-02-26 01:20:06 +03:00
# include "system/filesys.h"
2011-07-07 19:42:08 +04:00
# include "dbwrap/dbwrap.h"
2011-07-06 18:40:21 +04:00
# include "dbwrap/dbwrap_open.h"
2012-02-15 19:38:43 +04:00
# include "dbwrap/dbwrap_watch.h"
2009-10-25 18:12:12 +03:00
# include "g_lock.h"
2011-05-05 13:25:29 +04:00
# include "util_tdb.h"
2010-08-26 12:14:07 +04:00
# include "ctdbd_conn.h"
2010-10-01 12:08:15 +04:00
# include "../lib/util/select.h"
2012-02-15 19:38:43 +04:00
# include "../lib/util/tevent_ntstatus.h"
2011-02-08 13:59:04 +03:00
# include "system/select.h"
2011-03-24 17:31:06 +03:00
# include "messages.h"
2009-10-25 18:12:12 +03:00
struct g_lock_ctx {
struct db_context * db ;
struct messaging_context * msg ;
} ;
/*
* The " g_lock.tdb " file contains records , indexed by the 0 - terminated
* lockname . The record contains an array of " struct g_lock_rec "
2012-02-15 19:38:43 +04:00
* structures .
2009-10-25 18:12:12 +03:00
*/
struct g_lock_rec {
enum g_lock_type lock_type ;
struct server_id pid ;
} ;
struct g_lock_ctx * g_lock_ctx_init ( TALLOC_CTX * mem_ctx ,
struct messaging_context * msg )
{
struct g_lock_ctx * result ;
result = talloc ( mem_ctx , struct g_lock_ctx ) ;
if ( result = = NULL ) {
return NULL ;
}
result - > msg = msg ;
result - > db = db_open ( result , lock_path ( " g_lock.tdb " ) , 0 ,
2011-06-27 17:44:10 +04:00
TDB_CLEAR_IF_FIRST | TDB_INCOMPATIBLE_HASH ,
2012-01-06 20:19:54 +04:00
O_RDWR | O_CREAT , 0600 ,
DBWRAP_LOCK_ORDER_2 ) ;
2009-10-25 18:12:12 +03:00
if ( result - > db = = NULL ) {
2011-09-20 00:30:57 +04:00
DEBUG ( 1 , ( " g_lock_init: Could not open g_lock.tdb \n " ) ) ;
2009-10-25 18:12:12 +03:00
TALLOC_FREE ( result ) ;
return NULL ;
}
2012-02-15 19:38:43 +04:00
dbwrap_watch_db ( result - > db , msg ) ;
2009-10-25 18:12:12 +03:00
return result ;
}
2012-02-15 19:38:43 +04:00
static bool g_lock_conflicts ( enum g_lock_type l1 , enum g_lock_type l2 )
2009-10-25 18:12:12 +03:00
{
/*
* Only tested write locks so far . Very likely this routine
* needs to be fixed for read locks . . . .
*/
2012-02-15 19:38:43 +04:00
if ( ( l1 = = G_LOCK_READ ) & & ( l2 = = G_LOCK_READ ) ) {
2009-10-25 18:12:12 +03:00
return false ;
}
return true ;
}
static bool g_lock_parse ( TALLOC_CTX * mem_ctx , TDB_DATA data ,
2012-02-15 19:38:43 +04:00
unsigned * pnum_locks , struct g_lock_rec * * plocks )
2009-10-25 18:12:12 +03:00
{
2012-02-15 19:38:43 +04:00
unsigned num_locks ;
2009-10-25 18:12:12 +03:00
struct g_lock_rec * locks ;
if ( ( data . dsize % sizeof ( struct g_lock_rec ) ) ! = 0 ) {
DEBUG ( 1 , ( " invalid lock record length %d \n " , ( int ) data . dsize ) ) ;
return false ;
}
num_locks = data . dsize / sizeof ( struct g_lock_rec ) ;
2012-02-15 19:38:43 +04:00
locks = talloc_memdup ( mem_ctx , data . dptr , data . dsize ) ;
2009-10-25 18:12:12 +03:00
if ( locks = = NULL ) {
2012-02-15 19:38:43 +04:00
DEBUG ( 1 , ( " talloc_memdup failed \n " ) ) ;
2009-10-25 18:12:12 +03:00
return false ;
}
2010-02-15 18:57:16 +03:00
* plocks = locks ;
* pnum_locks = num_locks ;
return true ;
}
2012-02-15 19:38:43 +04:00
static NTSTATUS g_lock_trylock ( struct db_record * rec , struct server_id self ,
enum g_lock_type type )
2010-02-15 18:57:16 +03:00
{
2012-02-15 19:38:43 +04:00
TDB_DATA data ;
unsigned i , num_locks ;
struct g_lock_rec * locks , * tmp ;
NTSTATUS status ;
bool modified = false ;
2010-02-15 18:57:16 +03:00
2012-02-15 19:38:43 +04:00
data = dbwrap_record_get_value ( rec ) ;
2010-02-15 18:57:16 +03:00
2012-02-15 19:38:43 +04:00
if ( ! g_lock_parse ( talloc_tos ( ) , data , & num_locks , & locks ) ) {
return NT_STATUS_INTERNAL_ERROR ;
}
2010-02-15 18:57:16 +03:00
for ( i = 0 ; i < num_locks ; i + + ) {
2012-06-16 02:26:26 +04:00
if ( serverid_equal ( & self , & locks [ i ] . pid ) ) {
2012-02-15 19:38:43 +04:00
status = NT_STATUS_INTERNAL_ERROR ;
goto done ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
if ( g_lock_conflicts ( type , locks [ i ] . lock_type ) ) {
2009-10-25 18:12:12 +03:00
2012-02-15 19:38:43 +04:00
if ( process_exists ( locks [ i ] . pid ) ) {
status = NT_STATUS_LOCK_NOT_GRANTED ;
goto done ;
}
/*
* Delete stale conflicting entry
*/
2009-10-25 18:12:12 +03:00
locks [ i ] = locks [ num_locks - 1 ] ;
2012-02-15 19:38:43 +04:00
num_locks - = 1 ;
modified = true ;
2009-10-25 18:12:12 +03:00
}
}
2012-02-15 19:38:43 +04:00
tmp = talloc_realloc ( talloc_tos ( ) , locks , struct g_lock_rec ,
num_locks + 1 ) ;
if ( tmp = = NULL ) {
status = NT_STATUS_NO_MEMORY ;
goto done ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
locks = tmp ;
2009-10-25 18:12:12 +03:00
2012-02-15 19:38:43 +04:00
ZERO_STRUCT ( locks [ num_locks ] ) ;
locks [ num_locks ] . pid = self ;
locks [ num_locks ] . lock_type = type ;
num_locks + = 1 ;
modified = true ;
status = NT_STATUS_OK ;
done :
if ( modified ) {
NTSTATUS store_status ;
data = make_tdb_data ( ( uint8_t * ) locks , num_locks * sizeof ( * locks ) ) ;
store_status = dbwrap_record_store ( rec , data , 0 ) ;
if ( ! NT_STATUS_IS_OK ( store_status ) ) {
DEBUG ( 1 , ( " rec->store failed: %s \n " ,
nt_errstr ( store_status ) ) ) ;
status = store_status ;
}
}
TALLOC_FREE ( locks ) ;
return status ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
struct g_lock_lock_state {
struct tevent_context * ev ;
struct g_lock_ctx * ctx ;
const char * name ;
enum g_lock_type type ;
} ;
static void g_lock_lock_retry ( struct tevent_req * subreq ) ;
2009-10-25 18:12:12 +03:00
2012-02-15 19:38:43 +04:00
struct tevent_req * g_lock_lock_send ( TALLOC_CTX * mem_ctx ,
struct tevent_context * ev ,
struct g_lock_ctx * ctx ,
const char * name ,
enum g_lock_type type )
2009-10-25 18:12:12 +03:00
{
2012-02-15 19:38:43 +04:00
struct tevent_req * req , * subreq ;
struct g_lock_lock_state * state ;
struct db_record * rec ;
2009-10-25 18:12:12 +03:00
struct server_id self ;
2012-02-15 19:38:43 +04:00
NTSTATUS status ;
req = tevent_req_create ( mem_ctx , & state , struct g_lock_lock_state ) ;
if ( req = = NULL ) {
return NULL ;
}
state - > ev = ev ;
state - > ctx = ctx ;
state - > name = name ;
state - > type = type ;
2009-10-25 18:12:12 +03:00
2011-08-17 13:21:31 +04:00
rec = dbwrap_fetch_locked ( ctx - > db , talloc_tos ( ) ,
2012-02-15 19:38:43 +04:00
string_term_tdb_data ( state - > name ) ) ;
2009-10-25 18:12:12 +03:00
if ( rec = = NULL ) {
DEBUG ( 10 , ( " fetch_locked( \" %s \" ) failed \n " , name ) ) ;
2012-02-15 19:38:43 +04:00
tevent_req_nterror ( req , NT_STATUS_LOCK_NOT_GRANTED ) ;
return tevent_req_post ( req , ev ) ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
self = messaging_server_id ( state - > ctx - > msg ) ;
2009-10-25 18:12:12 +03:00
2012-02-15 19:38:43 +04:00
status = g_lock_trylock ( rec , self , state - > type ) ;
if ( NT_STATUS_IS_OK ( status ) ) {
TALLOC_FREE ( rec ) ;
tevent_req_done ( req ) ;
return tevent_req_post ( req , ev ) ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
if ( ! NT_STATUS_EQUAL ( status , NT_STATUS_LOCK_NOT_GRANTED ) ) {
TALLOC_FREE ( rec ) ;
tevent_req_nterror ( req , status ) ;
return tevent_req_post ( req , ev ) ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
subreq = dbwrap_record_watch_send ( state , state - > ev , rec ,
state - > ctx - > msg ) ;
2009-10-25 18:12:12 +03:00
TALLOC_FREE ( rec ) ;
2012-02-15 19:38:43 +04:00
if ( tevent_req_nomem ( subreq , req ) ) {
return tevent_req_post ( req , ev ) ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
tevent_req_set_callback ( subreq , g_lock_lock_retry , req ) ;
return req ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
static void g_lock_lock_retry ( struct tevent_req * subreq )
2009-10-25 18:12:12 +03:00
{
2012-02-15 19:38:43 +04:00
struct tevent_req * req = tevent_req_callback_data (
subreq , struct tevent_req ) ;
struct g_lock_lock_state * state = tevent_req_data (
req , struct g_lock_lock_state ) ;
struct server_id self = messaging_server_id ( state - > ctx - > msg ) ;
struct db_record * rec ;
2009-10-25 18:12:12 +03:00
NTSTATUS status ;
2012-02-15 19:38:43 +04:00
status = dbwrap_record_watch_recv ( subreq , talloc_tos ( ) , & rec ) ;
TALLOC_FREE ( subreq ) ;
if ( tevent_req_nterror ( req , status ) ) {
return ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
status = g_lock_trylock ( rec , self , state - > type ) ;
if ( NT_STATUS_IS_OK ( status ) ) {
TALLOC_FREE ( rec ) ;
tevent_req_done ( req ) ;
return ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
if ( ! NT_STATUS_EQUAL ( status , NT_STATUS_LOCK_NOT_GRANTED ) ) {
TALLOC_FREE ( rec ) ;
tevent_req_nterror ( req , status ) ;
return ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
subreq = dbwrap_record_watch_send ( state , state - > ev , rec ,
state - > ctx - > msg ) ;
2012-08-10 15:42:51 +04:00
TALLOC_FREE ( rec ) ;
2012-02-15 19:38:43 +04:00
if ( tevent_req_nomem ( subreq , req ) ) {
return ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
tevent_req_set_callback ( subreq , g_lock_lock_retry , req ) ;
return ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
NTSTATUS g_lock_lock_recv ( struct tevent_req * req )
2009-10-25 18:12:12 +03:00
{
2012-02-15 19:38:43 +04:00
return tevent_req_simple_recv_ntstatus ( req ) ;
}
2009-10-25 18:12:12 +03:00
2012-02-15 19:38:43 +04:00
NTSTATUS g_lock_lock ( struct g_lock_ctx * ctx , const char * name ,
enum g_lock_type type , struct timeval timeout )
{
TALLOC_CTX * frame = talloc_stackframe ( ) ;
struct tevent_context * ev ;
struct tevent_req * req ;
struct timeval end ;
NTSTATUS status = NT_STATUS_NO_MEMORY ;
2009-10-25 18:12:12 +03:00
2012-02-15 19:38:43 +04:00
ev = tevent_context_init ( frame ) ;
if ( ev = = NULL ) {
goto fail ;
}
req = g_lock_lock_send ( frame , ev , ctx , name , type ) ;
if ( req = = NULL ) {
goto fail ;
}
end = timeval_current_ofs ( timeout . tv_sec , timeout . tv_usec ) ;
if ( ! tevent_req_set_endtime ( req , ev , end ) ) {
goto fail ;
}
if ( ! tevent_req_poll_ntstatus ( req , ev , & status ) ) {
goto fail ;
}
status = g_lock_lock_recv ( req ) ;
fail :
TALLOC_FREE ( frame ) ;
return status ;
2009-10-25 18:12:12 +03:00
}
2012-02-15 19:38:43 +04:00
NTSTATUS g_lock_unlock ( struct g_lock_ctx * ctx , const char * name )
2009-10-25 18:12:12 +03:00
{
2012-02-15 19:38:43 +04:00
struct server_id self = messaging_server_id ( ctx - > msg ) ;
2009-10-25 18:12:12 +03:00
struct db_record * rec = NULL ;
struct g_lock_rec * locks = NULL ;
2012-02-15 19:38:43 +04:00
unsigned i , num_locks ;
2009-10-25 18:12:12 +03:00
NTSTATUS status ;
2011-08-17 13:21:31 +04:00
TDB_DATA value ;
2009-10-25 18:12:12 +03:00
2011-08-17 13:21:31 +04:00
rec = dbwrap_fetch_locked ( ctx - > db , talloc_tos ( ) ,
string_term_tdb_data ( name ) ) ;
2009-10-25 18:12:12 +03:00
if ( rec = = NULL ) {
DEBUG ( 10 , ( " fetch_locked( \" %s \" ) failed \n " , name ) ) ;
status = NT_STATUS_INTERNAL_ERROR ;
goto done ;
}
2011-08-17 13:21:31 +04:00
value = dbwrap_record_get_value ( rec ) ;
if ( ! g_lock_parse ( talloc_tos ( ) , value , & num_locks , & locks ) ) {
2009-10-25 18:12:12 +03:00
DEBUG ( 10 , ( " g_lock_parse for %s failed \n " , name ) ) ;
2011-08-31 12:19:18 +04:00
status = NT_STATUS_FILE_INVALID ;
2009-10-25 18:12:12 +03:00
goto done ;
}
for ( i = 0 ; i < num_locks ; i + + ) {
2012-06-16 02:26:26 +04:00
if ( serverid_equal ( & self , & locks [ i ] . pid ) ) {
2009-10-25 18:12:12 +03:00
break ;
}
}
if ( i = = num_locks ) {
DEBUG ( 10 , ( " g_lock_force_unlock: Lock not found \n " ) ) ;
2011-08-31 12:19:18 +04:00
status = NT_STATUS_NOT_FOUND ;
2009-10-25 18:12:12 +03:00
goto done ;
}
2012-02-15 19:38:43 +04:00
locks [ i ] = locks [ num_locks - 1 ] ;
2009-10-25 18:12:12 +03:00
num_locks - = 1 ;
if ( num_locks = = 0 ) {
2011-08-17 13:21:31 +04:00
status = dbwrap_record_delete ( rec ) ;
2009-10-25 18:12:12 +03:00
} else {
TDB_DATA data ;
data = make_tdb_data ( ( uint8_t * ) locks ,
sizeof ( struct g_lock_rec ) * num_locks ) ;
2011-08-17 13:21:31 +04:00
status = dbwrap_record_store ( rec , data , 0 ) ;
2009-10-25 18:12:12 +03:00
}
if ( ! NT_STATUS_IS_OK ( status ) ) {
DEBUG ( 1 , ( " g_lock_force_unlock: Could not store record: %s \n " ,
nt_errstr ( status ) ) ) ;
goto done ;
}
2012-02-15 19:38:43 +04:00
status = NT_STATUS_OK ;
2009-10-25 18:12:12 +03:00
done :
2010-02-16 14:31:58 +03:00
TALLOC_FREE ( rec ) ;
2009-10-25 18:12:12 +03:00
TALLOC_FREE ( locks ) ;
return status ;
}
struct g_lock_locks_state {
int ( * fn ) ( const char * name , void * private_data ) ;
void * private_data ;
} ;
static int g_lock_locks_fn ( struct db_record * rec , void * priv )
{
2011-08-17 13:21:31 +04:00
TDB_DATA key ;
2009-10-25 18:12:12 +03:00
struct g_lock_locks_state * state = ( struct g_lock_locks_state * ) priv ;
2011-08-17 13:21:31 +04:00
key = dbwrap_record_get_key ( rec ) ;
if ( ( key . dsize = = 0 ) | | ( key . dptr [ key . dsize - 1 ] ! = 0 ) ) {
2009-10-25 18:12:12 +03:00
DEBUG ( 1 , ( " invalid key in g_lock.tdb, ignoring \n " ) ) ;
return 0 ;
}
2011-08-17 13:21:31 +04:00
return state - > fn ( ( char * ) key . dptr , state - > private_data ) ;
2009-10-25 18:12:12 +03:00
}
int g_lock_locks ( struct g_lock_ctx * ctx ,
int ( * fn ) ( const char * name , void * private_data ) ,
void * private_data )
{
struct g_lock_locks_state state ;
2011-08-17 13:21:31 +04:00
NTSTATUS status ;
int count ;
2009-10-25 18:12:12 +03:00
state . fn = fn ;
state . private_data = private_data ;
2011-08-17 13:21:31 +04:00
status = dbwrap_traverse_read ( ctx - > db , g_lock_locks_fn , & state , & count ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
return - 1 ;
} else {
return count ;
}
2009-10-25 18:12:12 +03:00
}
NTSTATUS g_lock_dump ( struct g_lock_ctx * ctx , const char * name ,
int ( * fn ) ( struct server_id pid ,
enum g_lock_type lock_type ,
void * private_data ) ,
void * private_data )
{
TDB_DATA data ;
2012-02-15 19:38:43 +04:00
unsigned i , num_locks ;
2009-10-25 18:12:12 +03:00
struct g_lock_rec * locks = NULL ;
bool ret ;
2011-08-17 13:21:31 +04:00
NTSTATUS status ;
2009-10-25 18:12:12 +03:00
2011-08-17 13:21:31 +04:00
status = dbwrap_fetch_bystring ( ctx - > db , talloc_tos ( ) , name , & data ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
return status ;
2009-10-25 18:12:12 +03:00
}
if ( ( data . dsize = = 0 ) | | ( data . dptr = = NULL ) ) {
return NT_STATUS_OK ;
}
ret = g_lock_parse ( talloc_tos ( ) , data , & num_locks , & locks ) ;
TALLOC_FREE ( data . dptr ) ;
if ( ! ret ) {
DEBUG ( 10 , ( " g_lock_parse for %s failed \n " , name ) ) ;
return NT_STATUS_INTERNAL_ERROR ;
}
for ( i = 0 ; i < num_locks ; i + + ) {
if ( fn ( locks [ i ] . pid , locks [ i ] . lock_type , private_data ) ! = 0 ) {
break ;
}
}
TALLOC_FREE ( locks ) ;
return NT_STATUS_OK ;
}
struct g_lock_get_state {
bool found ;
struct server_id * pid ;
} ;
static int g_lock_get_fn ( struct server_id pid , enum g_lock_type lock_type ,
void * priv )
{
struct g_lock_get_state * state = ( struct g_lock_get_state * ) priv ;
state - > found = true ;
* state - > pid = pid ;
return 1 ;
}
NTSTATUS g_lock_get ( struct g_lock_ctx * ctx , const char * name ,
struct server_id * pid )
{
struct g_lock_get_state state ;
NTSTATUS status ;
state . found = false ;
state . pid = pid ;
status = g_lock_dump ( ctx , name , g_lock_get_fn , & state ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
return status ;
}
if ( ! state . found ) {
return NT_STATUS_NOT_FOUND ;
}
return NT_STATUS_OK ;
}
2010-03-12 16:22:54 +03:00
static bool g_lock_init_all ( TALLOC_CTX * mem_ctx ,
struct tevent_context * * pev ,
struct messaging_context * * pmsg ,
struct g_lock_ctx * * pg_ctx )
{
struct tevent_context * ev = NULL ;
struct messaging_context * msg = NULL ;
struct g_lock_ctx * g_ctx = NULL ;
ev = tevent_context_init ( mem_ctx ) ;
if ( ev = = NULL ) {
d_fprintf ( stderr , " ERROR: could not init event context \n " ) ;
goto fail ;
}
2011-12-12 17:55:54 +04:00
msg = messaging_init ( mem_ctx , ev ) ;
2010-03-12 16:22:54 +03:00
if ( msg = = NULL ) {
d_fprintf ( stderr , " ERROR: could not init messaging context \n " ) ;
goto fail ;
}
g_ctx = g_lock_ctx_init ( mem_ctx , msg ) ;
if ( g_ctx = = NULL ) {
d_fprintf ( stderr , " ERROR: could not init g_lock context \n " ) ;
goto fail ;
}
* pev = ev ;
* pmsg = msg ;
* pg_ctx = g_ctx ;
return true ;
fail :
TALLOC_FREE ( g_ctx ) ;
TALLOC_FREE ( msg ) ;
TALLOC_FREE ( ev ) ;
return false ;
}
NTSTATUS g_lock_do ( const char * name , enum g_lock_type lock_type ,
2011-12-12 17:55:54 +04:00
struct timeval timeout ,
2010-03-12 16:22:54 +03:00
void ( * fn ) ( void * private_data ) , void * private_data )
{
struct tevent_context * ev = NULL ;
struct messaging_context * msg = NULL ;
struct g_lock_ctx * g_ctx = NULL ;
NTSTATUS status ;
2011-12-12 17:55:54 +04:00
if ( ! g_lock_init_all ( talloc_tos ( ) , & ev , & msg , & g_ctx ) ) {
2010-03-12 16:22:54 +03:00
status = NT_STATUS_ACCESS_DENIED ;
goto done ;
}
status = g_lock_lock ( g_ctx , name , lock_type , timeout ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
goto done ;
}
fn ( private_data ) ;
g_lock_unlock ( g_ctx , name ) ;
done :
TALLOC_FREE ( g_ctx ) ;
TALLOC_FREE ( msg ) ;
TALLOC_FREE ( ev ) ;
return status ;
}