468 lines
17 KiB
C
468 lines
17 KiB
C
/****************************************************************
|
|
* *
|
|
* This source code contains the intellectual property *
|
|
* of its copyright holder(s), and is made available *
|
|
* under a license. If you do not know the terms of *
|
|
* the license, please stop and do not read further. *
|
|
* *
|
|
****************************************************************/
|
|
|
|
#include "mdef.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#include "gtm_string.h"
|
|
#include "gtm_stdio.h"
|
|
#include "gtm_socket.h"
|
|
#include "gtm_inet.h"
|
|
#include "gtm_time.h"
|
|
#include "copy.h"
|
|
#include "gt_timer.h"
|
|
#include "io.h"
|
|
#include "iotimer.h"
|
|
#include "iotcp_select.h"
|
|
#include "iotcpdef.h"
|
|
#include "iotcproutine.h"
|
|
#include "io_params.h"
|
|
#include "iosocketdef.h"
|
|
#include "gtm_caseconv.h"
|
|
#include "stringpool.h"
|
|
#include "gtm_conv.h"
|
|
#include "gtm_utf8.h"
|
|
|
|
GBLREF tcp_library_struct tcp_routines;
|
|
GBLREF d_socket_struct *socket_pool, *newdsocket;
|
|
GBLREF io_pair io_std_device; /* standard device */
|
|
GBLREF boolean_t gtm_utf8_mode;
|
|
GBLREF int4 gtm_max_sockets;
|
|
GBLREF boolean_t dollar_zininterrupt;
|
|
LITREF unsigned char io_params_size[];
|
|
LITREF mstr chset_names[];
|
|
|
|
error_def(ERR_ABNCOMPTINC);
|
|
error_def(ERR_ADDRTOOLONG);
|
|
error_def(ERR_SOCKETEXIST);
|
|
error_def(ERR_DELIMSIZNA);
|
|
error_def(ERR_DELIMWIDTH);
|
|
error_def(ERR_DEVPARINAP);
|
|
error_def(ERR_DEVPARMNEG);
|
|
error_def(ERR_ILLESOCKBFSIZE);
|
|
error_def(ERR_MRTMAXEXCEEDED);
|
|
error_def(ERR_SOCKMAX);
|
|
error_def(ERR_ZFF2MANY);
|
|
error_def(ERR_ZINTRECURSEIO);
|
|
|
|
#define ESTABLISHED "ESTABLISHED"
|
|
|
|
#define FREE_SOCKPTR(sockptr) \
|
|
{ \
|
|
if (NULL != sockptr->buffer) free(sockptr->buffer); \
|
|
free(sockptr); \
|
|
}
|
|
|
|
short iosocket_open(io_log_name *dev, mval *pp, int file_des, mval *mspace, int4 timepar)
|
|
{
|
|
char addr[SA_MAXLITLEN], *errptr, sockaddr[SA_MAXLITLEN],
|
|
temp_addr[SA_MAXLITLEN], dev_type[MAX_DEV_TYPE_LEN];
|
|
unsigned char ch, *c, *next, *top;
|
|
int handle_len, moreread_timeout, len;
|
|
unsigned short port;
|
|
int4 errlen, msec_timeout, real_errno, p_offset = 0, zff_len, delimiter_len;
|
|
int d_socket_struct_len;
|
|
ABS_TIME cur_time, end_time;
|
|
io_desc *ioptr;
|
|
struct sockaddr_in peer; /* socket address + port */
|
|
fd_set tcp_fd;
|
|
uint4 bfsize = DEFAULT_SOCKET_BUFFER_SIZE, ibfsize;
|
|
d_socket_struct *dsocketptr;
|
|
socket_struct *socketptr;
|
|
mv_stent *mv_zintdev;
|
|
boolean_t zint_conn_restart = FALSE;
|
|
socket_interrupt *sockintr;
|
|
mstr chset_mstr;
|
|
boolean_t attach_specified = FALSE,
|
|
listen_specified = FALSE,
|
|
connect_specified = FALSE,
|
|
ioerror_specified = FALSE,
|
|
delay_specified = FALSE,
|
|
nodelay_specified = FALSE,
|
|
ibfsize_specified = FALSE,
|
|
moreread_specified = FALSE,
|
|
is_principal = FALSE, /* called from inetd */
|
|
ichset_specified,
|
|
ochset_specified;
|
|
unsigned char delimiter_buffer[MAX_N_DELIMITER * (MAX_DELIM_LEN + 1)], zff_buffer[MAX_ZFF_LEN];
|
|
char ioerror, ip[3], tcp[4],
|
|
sock_handle[MAX_HANDLE_LEN], delimiter[MAX_DELIM_LEN + 1];
|
|
int socketptr_delim_len;
|
|
|
|
ioptr = dev->iod;
|
|
assert((params) *(pp->str.addr + p_offset) < (unsigned char)n_iops);
|
|
assert(ioptr != 0);
|
|
assert(ioptr->state >= 0 && ioptr->state < n_io_dev_states);
|
|
assert(ioptr->type == gtmsocket);
|
|
if ((ioptr->state == dev_closed) && mspace && mspace->str.len && mspace->str.addr)
|
|
{
|
|
lower_to_upper((uchar_ptr_t)dev_type, (uchar_ptr_t)mspace->str.addr, mspace->str.len);
|
|
if (STR_LIT_LEN("SOCKET") != mspace->str.len || 0 != memcmp(dev_type, "SOCKET", STR_LIT_LEN("SOCKET")))
|
|
{
|
|
if (ioptr->dev_sp)
|
|
free(ioptr->dev_sp);
|
|
ioptr->state = dev_never_opened;
|
|
}
|
|
}
|
|
d_socket_struct_len = SIZEOF(d_socket_struct) + (SIZEOF(socket_struct) * (gtm_max_sockets - 1));
|
|
if (ioptr->state == dev_never_opened)
|
|
{
|
|
dsocketptr = ioptr->dev_sp = (void *)malloc(d_socket_struct_len);
|
|
ioptr->newly_created = TRUE;
|
|
memset(dsocketptr, 0, d_socket_struct_len);
|
|
dsocketptr->iod = ioptr;
|
|
} else
|
|
dsocketptr = (d_socket_struct *)ioptr->dev_sp;
|
|
|
|
if (ioptr->state == dev_never_opened)
|
|
{
|
|
ioptr->state = dev_closed;
|
|
ioptr->width = TCPDEF_WIDTH;
|
|
ioptr->length = TCPDEF_LENGTH;
|
|
ioptr->wrap = TRUE;
|
|
if (-1 == iotcp_fillroutine())
|
|
assert(FALSE);
|
|
if (!io_std_device.in)
|
|
/* called from io_init */
|
|
is_principal = TRUE;
|
|
}
|
|
if (dsocketptr->mupintr)
|
|
{ /* check if connect was interrupted */
|
|
sockintr = &dsocketptr->sock_save_state;
|
|
if (sockwhich_invalid == sockintr->who_saved)
|
|
GTMASSERT; /* Interrupt should never have an invalid save state */
|
|
if (dollar_zininterrupt)
|
|
{
|
|
dsocketptr->mupintr = FALSE;
|
|
sockintr->who_saved = sockwhich_invalid;
|
|
rts_error(VARLSTCNT(1) ERR_ZINTRECURSEIO);
|
|
}
|
|
if (sockwhich_connect != sockintr->who_saved)
|
|
GTMASSERT; /* ZINTRECURSEIO should have caught */
|
|
mv_zintdev = io_find_mvstent(dsocketptr->iod, FALSE);
|
|
if (mv_zintdev && mv_zintdev->mv_st_cont.mvs_zintdev.buffer_valid)
|
|
{ /* mupintr will be reset and mvstent popped in iosocket_connect */
|
|
connect_specified = TRUE;
|
|
ibfsize_specified = sockintr->ibfsize_specified;
|
|
assert(newdsocket);
|
|
assert(newdsocket == sockintr->newdsocket);
|
|
memcpy(newdsocket, (d_socket_struct *)mv_zintdev->mv_st_cont.mvs_zintdev.curr_sp_buffer.addr,
|
|
d_socket_struct_len);
|
|
socketptr = newdsocket->socket[newdsocket->current_socket];
|
|
assert(socketptr == (socket_struct *)mv_zintdev->mv_st_cont.mvs_zintdev.socketptr);
|
|
zint_conn_restart = TRUE; /* skip what we already did, state == dev_closed */
|
|
}
|
|
} else
|
|
{
|
|
ioptr->dollar.zeof = FALSE;
|
|
if (NULL == newdsocket)
|
|
newdsocket = (d_socket_struct *)malloc(d_socket_struct_len);
|
|
memcpy(newdsocket, dsocketptr, d_socket_struct_len);
|
|
memcpy(newdsocket->dollar_device, "0", SIZEOF("0"));
|
|
zff_len = -1; /* indicates neither ZFF nor ZNOFF specified */
|
|
delimiter_len = -1; /* indicates neither DELIM nor NODELIM specified */
|
|
ichset_specified = ochset_specified = FALSE;
|
|
while (iop_eol != (ch = *(pp->str.addr + p_offset++)))
|
|
{
|
|
switch(ch)
|
|
{
|
|
case iop_delimiter:
|
|
delimiter_len = (int4)(unsigned char)*(pp->str.addr + p_offset);
|
|
if (((MAX_DELIM_LEN + 1) * MAX_N_DELIMITER) >= delimiter_len)
|
|
memcpy(delimiter_buffer, (pp->str.addr + p_offset + 1), delimiter_len);
|
|
else
|
|
rts_error(VARLSTCNT(1) ERR_DELIMSIZNA);
|
|
break;
|
|
case iop_ipchset:
|
|
UNICODE_ONLY(
|
|
if (gtm_utf8_mode)
|
|
{ /* Only change ipchset if in UTF8 mode */
|
|
chset_mstr.addr = (char *)(pp->str.addr + p_offset + 1);
|
|
chset_mstr.len = *(pp->str.addr + p_offset);
|
|
SET_ENCODING(ioptr->ichset, &chset_mstr);
|
|
ichset_specified = TRUE;
|
|
}
|
|
);
|
|
break;
|
|
case iop_opchset:
|
|
UNICODE_ONLY(
|
|
if (gtm_utf8_mode)
|
|
{ /* Only change ipchset if in UTF8 mode */
|
|
chset_mstr.addr = (char *)(pp->str.addr + p_offset + 1);
|
|
chset_mstr.len = *(pp->str.addr + p_offset);
|
|
SET_ENCODING(ioptr->ochset, &chset_mstr);
|
|
ochset_specified = TRUE;
|
|
}
|
|
);
|
|
break;
|
|
case iop_chset:
|
|
UNICODE_ONLY(
|
|
if (gtm_utf8_mode)
|
|
{ /* Only change ipchset/opchset if in UTF8 mode */
|
|
chset_mstr.addr = (char *)(pp->str.addr + p_offset + 1);
|
|
chset_mstr.len = *(pp->str.addr + p_offset);
|
|
SET_ENCODING(ioptr->ichset, &chset_mstr);
|
|
ioptr->ochset = ioptr->ichset;
|
|
ichset_specified = ochset_specified = TRUE;
|
|
}
|
|
);
|
|
break;
|
|
/* Note the following 4 cases (iop_m/utf16/utf16be/utf16le) have no corresponding device parameter
|
|
but are included here because they can be easily used in internal processing.
|
|
*/
|
|
case iop_m:
|
|
UNICODE_ONLY(
|
|
ioptr->ichset = ioptr->ochset = CHSET_M;
|
|
ichset_specified = ochset_specified = TRUE;
|
|
);
|
|
break;
|
|
case iop_utf16:
|
|
UNICODE_ONLY(
|
|
if (gtm_utf8_mode)
|
|
{ /* Only change chset if in UTF8 mode */
|
|
ioptr->ichset = ioptr->ochset = CHSET_UTF16;
|
|
ichset_specified = ochset_specified = TRUE;
|
|
}
|
|
);
|
|
break;
|
|
case iop_utf16be:
|
|
UNICODE_ONLY(
|
|
if (gtm_utf8_mode)
|
|
{ /* Only change chset if in UTF8 mode */
|
|
ioptr->ichset = ioptr->ochset = CHSET_UTF16BE;
|
|
ichset_specified = ochset_specified = TRUE;
|
|
}
|
|
);
|
|
break;
|
|
case iop_utf16le:
|
|
UNICODE_ONLY(
|
|
if (gtm_utf8_mode)
|
|
{ /* Only change chset if in UTF8 mode */
|
|
ioptr->ichset = ioptr->ochset = CHSET_UTF16LE;
|
|
ichset_specified = ochset_specified = TRUE;
|
|
}
|
|
);
|
|
break;
|
|
/**********************************/
|
|
case iop_nodelimiter:
|
|
delimiter_len = 0;
|
|
break;
|
|
case iop_zdelay:
|
|
delay_specified = TRUE;
|
|
break;
|
|
case iop_znodelay:
|
|
nodelay_specified = TRUE;
|
|
break;
|
|
case iop_zbfsize:
|
|
GET_ULONG(bfsize, pp->str.addr + p_offset);
|
|
if ((0 == bfsize) || (MAX_SOCKET_BUFFER_SIZE < bfsize))
|
|
rts_error(VARLSTCNT(3) ERR_ILLESOCKBFSIZE, 1, bfsize);
|
|
break;
|
|
case iop_zibfsize:
|
|
ibfsize_specified = TRUE;
|
|
GET_ULONG(ibfsize, pp->str.addr + p_offset);
|
|
if ((0 == ibfsize) || (MAX_INTERNAL_SOCBUF_SIZE < ibfsize))
|
|
rts_error(VARLSTCNT(3) ERR_ILLESOCKBFSIZE, 1, bfsize);
|
|
break;
|
|
case iop_zlisten:
|
|
listen_specified = TRUE;
|
|
len = (int)(*(pp->str.addr + p_offset));
|
|
if (len < SA_MAXLITLEN)
|
|
{
|
|
memset(sockaddr, 0, SIZEOF(sockaddr));
|
|
memcpy(sockaddr, pp->str.addr + p_offset + 1, len);
|
|
} else
|
|
rts_error(VARLSTCNT(6) ERR_ADDRTOOLONG, 4, len, pp->str.addr + p_offset + 1,
|
|
len, SA_MAXLITLEN);
|
|
break;
|
|
case iop_connect:
|
|
connect_specified = TRUE;
|
|
len = (int)(*(pp->str.addr + p_offset));
|
|
if (len < SA_MAXLITLEN)
|
|
{
|
|
memset(sockaddr, 0, SIZEOF(sockaddr));
|
|
memcpy(sockaddr, pp->str.addr + p_offset + 1, len);
|
|
} else
|
|
rts_error(VARLSTCNT(6) ERR_ADDRTOOLONG, 4, len, pp->str.addr + p_offset + 1,
|
|
len, SA_MAXLITLEN);
|
|
break;
|
|
case iop_ioerror:
|
|
ioerror_specified = TRUE;
|
|
ioerror = *(pp->str.addr + p_offset + 1); /* the first char decides */
|
|
break;
|
|
case iop_exception:
|
|
ioptr->error_handler.len = *(pp->str.addr + p_offset);
|
|
ioptr->error_handler.addr = (char *)(pp->str.addr + p_offset + 1);
|
|
s2pool(&ioptr->error_handler);
|
|
break;
|
|
case iop_attach:
|
|
attach_specified = TRUE;
|
|
handle_len = (int)(*(pp->str.addr + p_offset));
|
|
if (handle_len > MAX_HANDLE_LEN)
|
|
handle_len = MAX_HANDLE_LEN;
|
|
memcpy(sock_handle, pp->str.addr + p_offset + 1, handle_len);
|
|
break;
|
|
case iop_socket:
|
|
rts_error(VARLSTCNT(1) ERR_DEVPARINAP);
|
|
break;
|
|
case iop_zff:
|
|
if (MAX_ZFF_LEN >= (zff_len = (int4)(unsigned char)*(pp->str.addr + p_offset)))
|
|
memcpy(zff_buffer, (char *)(pp->str.addr + p_offset + 1), zff_len);
|
|
else
|
|
rts_error(VARLSTCNT(4) ERR_ZFF2MANY, 2, zff_len, MAX_ZFF_LEN);
|
|
break;
|
|
case iop_znoff:
|
|
zff_len = 0;
|
|
break;
|
|
case iop_wrap:
|
|
ioptr->wrap = TRUE;
|
|
break;
|
|
case iop_nowrap:
|
|
ioptr->wrap = FALSE;
|
|
break;
|
|
case iop_morereadtime:
|
|
/* Time in milliseconds socket read will wait for more data before returning */
|
|
GET_LONG(moreread_timeout, pp->str.addr + p_offset);
|
|
if (-1 == moreread_timeout)
|
|
moreread_timeout = DEFAULT_MOREREAD_TIMEOUT;
|
|
else if (-1 > moreread_timeout)
|
|
rts_error(VARLSTCNT(1) ERR_DEVPARMNEG);
|
|
else if (MAX_MOREREAD_TIMEOUT < moreread_timeout)
|
|
rts_error(VARLSTCNT(3) ERR_MRTMAXEXCEEDED, 1, MAX_MOREREAD_TIMEOUT);
|
|
moreread_specified = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
p_offset += ((IOP_VAR_SIZE == io_params_size[ch]) ?
|
|
(unsigned char)*(pp->str.addr + p_offset) + 1 : io_params_size[ch]);
|
|
}
|
|
if (!ichset_specified)
|
|
ioptr->ichset = (gtm_utf8_mode) ? CHSET_UTF8 : CHSET_M;
|
|
if (!ochset_specified)
|
|
ioptr->ochset = (gtm_utf8_mode) ? CHSET_UTF8 : CHSET_M;
|
|
if (CHSET_M != ioptr->ichset && CHSET_UTF16 != ioptr->ichset)
|
|
get_chset_desc(&chset_names[ioptr->ichset]);
|
|
if (CHSET_M != ioptr->ochset && CHSET_UTF16 != ioptr->ochset)
|
|
get_chset_desc(&chset_names[ioptr->ochset]);
|
|
if (listen_specified && connect_specified)
|
|
{
|
|
rts_error(VARLSTCNT(8) ERR_ABNCOMPTINC, 6, LEN_AND_LIT("CONNECT"),
|
|
LEN_AND_LIT("ZLISTEN"), LEN_AND_LIT("OPEN"));
|
|
return FALSE;
|
|
}
|
|
if (delay_specified && nodelay_specified)
|
|
{
|
|
rts_error(VARLSTCNT(8) ERR_ABNCOMPTINC, 6, LEN_AND_LIT("DELAY"),
|
|
LEN_AND_LIT("NODELAY"), LEN_AND_LIT("OPEN"));
|
|
return FALSE;
|
|
}
|
|
if (listen_specified || connect_specified || is_principal)
|
|
{
|
|
if (NULL == (socketptr = iosocket_create(sockaddr, bfsize, is_principal ? file_des : -1)))
|
|
return FALSE;
|
|
assert(listen_specified == socketptr->passive);
|
|
if (ioerror_specified)
|
|
socketptr->ioerror = ('T' == ioerror || 't' == ioerror);
|
|
socketptr->nodelay = nodelay_specified; /* defaults to DELAY */
|
|
if (ibfsize_specified)
|
|
socketptr->bufsiz = ibfsize;
|
|
if (moreread_specified)
|
|
{
|
|
socketptr->moreread_timeout = moreread_timeout;
|
|
socketptr->def_moreread_timeout = TRUE; /* iosocket_readfl.c needs to know user specified */
|
|
}
|
|
/* socket handle -- also check for duplication */
|
|
if (attach_specified)
|
|
{
|
|
if (iosocket_handle(sock_handle, &handle_len, FALSE, newdsocket) >= 0)
|
|
{
|
|
FREE_SOCKPTR(socketptr);
|
|
assert(ioptr->newly_created == FALSE);
|
|
rts_error(VARLSTCNT(4) ERR_SOCKETEXIST, 2, handle_len, sock_handle);
|
|
return FALSE;
|
|
}
|
|
} else
|
|
iosocket_handle(sock_handle, &handle_len, TRUE, dsocketptr);
|
|
socketptr->handle_len = handle_len;
|
|
memcpy(socketptr->handle, sock_handle, handle_len);
|
|
/* parse the delimiter: delimiter_buffer ==> socketptr->delimiter[...] */
|
|
if (0 <= delimiter_len)
|
|
iosocket_delimiter(delimiter_buffer, delimiter_len, socketptr, (0 == delimiter_len));
|
|
if (ioptr->wrap && 0 != socketptr->n_delimiter && ioptr->width < socketptr->delimiter[0].len)
|
|
{
|
|
socketptr_delim_len = socketptr->delimiter[0].len;
|
|
iosocket_delimiter((unsigned char *)NULL, 0, socketptr, TRUE);
|
|
FREE_SOCKPTR(socketptr);
|
|
rts_error(VARLSTCNT(4) ERR_DELIMWIDTH, 2, ioptr->width, socketptr_delim_len);
|
|
assert(FALSE);
|
|
}
|
|
/* connects newdsocket and socketptr (the new socket) */
|
|
if (gtm_max_sockets <= newdsocket->n_socket)
|
|
{
|
|
assert(ioptr->newly_created == FALSE);
|
|
iosocket_delimiter((unsigned char *)NULL, 0, socketptr, TRUE);
|
|
FREE_SOCKPTR(socketptr);
|
|
rts_error(VARLSTCNT(3) ERR_SOCKMAX, 1, gtm_max_sockets);
|
|
return FALSE;
|
|
}
|
|
socketptr->dev = newdsocket;
|
|
newdsocket->socket[newdsocket->n_socket++] = socketptr;
|
|
newdsocket->current_socket = newdsocket->n_socket - 1;
|
|
}
|
|
if (0 <= zff_len && /* ZFF or ZNOFF specified */
|
|
0 < (socketptr->zff.len = zff_len)) /* assign the new ZFF len, might be 0 from ZNOFF, or ZFF="" */
|
|
{ /* ZFF="non-zero-len-string" specified */
|
|
if (gtm_utf8_mode) /* Check if ZFF has any invalid UTF-8 character */
|
|
{ /* Note: the ZFF string originates from the source program, so is in UTF-8 mode or M mode regardless
|
|
* of OCHSET of this device. ZFF is output on WRITE # command, and MUST contain valid UTF-8 sequence. */
|
|
utf8_len_strict(zff_buffer, zff_len); /* triggers badchar error for invalid sequence */
|
|
}
|
|
if (NULL == socketptr->zff.addr) /* we rely on socketptr->zff.addr being set to 0 in iosocket_create() */
|
|
socketptr->zff.addr = (char *)malloc(MAX_ZFF_LEN);
|
|
memcpy(socketptr->zff.addr, zff_buffer, zff_len);
|
|
}
|
|
}
|
|
/* action */
|
|
if ((listen_specified && (!iosocket_bind(socketptr, timepar, ibfsize_specified))) ||
|
|
(connect_specified && (!iosocket_connect(socketptr, timepar, ibfsize_specified))))
|
|
{
|
|
if (socketptr->sd > 0)
|
|
(void)tcp_routines.aa_close(socketptr->sd);
|
|
iosocket_delimiter((unsigned char *)NULL, 0, socketptr, TRUE);
|
|
if (NULL != socketptr->zff.addr)
|
|
free(socketptr->zff.addr);
|
|
FREE_SOCKPTR(socketptr);
|
|
return FALSE;
|
|
} else if (is_principal)
|
|
{ /* fill in what bind or connect would */
|
|
strncpy(socketptr->local.saddr_ip, tcp_routines.aa_inet_ntoa(socketptr->local.sin.sin_addr),
|
|
SIZEOF(socketptr->local.saddr_ip));
|
|
strncpy(socketptr->remote.saddr_ip, tcp_routines.aa_inet_ntoa(socketptr->remote.sin.sin_addr),
|
|
SIZEOF(socketptr->remote.saddr_ip));
|
|
len = SIZEOF(ESTABLISHED) - 1;
|
|
memcpy(&newdsocket->dollar_key[0], ESTABLISHED, len);
|
|
newdsocket->dollar_key[len++] = '|';
|
|
memcpy(&newdsocket->dollar_key[len], socketptr->handle, socketptr->handle_len);
|
|
len += socketptr->handle_len;
|
|
newdsocket->dollar_key[len++] = '|';
|
|
strcpy(&newdsocket->dollar_key[len], socketptr->remote.saddr_ip); /* Copies in terminating NULL */
|
|
}
|
|
/* commit the changes to the list */
|
|
if (listen_specified || connect_specified || is_principal)
|
|
{
|
|
socketptr->dev = dsocketptr;
|
|
memcpy(dsocketptr, newdsocket, d_socket_struct_len);
|
|
}
|
|
ioptr->newly_created = FALSE;
|
|
ioptr->state = dev_open;
|
|
return TRUE;
|
|
}
|