2014-04-25 02:20:48 +04:00
|
|
|
/**
|
|
|
|
* WinPR: Windows Portable Runtime
|
|
|
|
* Serial Communication API
|
|
|
|
*
|
|
|
|
* Copyright 2011 O.S. Systems Software Ltda.
|
|
|
|
* Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
|
|
|
|
* Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
|
|
|
* Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef _WIN32
|
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
|
2014-04-25 02:20:48 +04:00
|
|
|
#include <assert.h>
|
2014-04-28 21:57:17 +04:00
|
|
|
#include <errno.h>
|
2014-04-25 02:20:48 +04:00
|
|
|
|
2014-07-02 17:59:16 +04:00
|
|
|
#include <winpr/wlog.h>
|
2014-04-25 02:20:48 +04:00
|
|
|
|
|
|
|
#include "comm.h"
|
|
|
|
#include "comm_ioctl.h"
|
|
|
|
#include "comm_serial_sys.h"
|
|
|
|
#include "comm_sercx_sys.h"
|
|
|
|
#include "comm_sercx2_sys.h"
|
|
|
|
|
|
|
|
|
2014-04-27 21:41:25 +04:00
|
|
|
/* NB: MS-RDPESP's recommendation:
|
|
|
|
*
|
|
|
|
* <2> Section 3.2.5.1.6: Windows Implementations use IOCTL constants
|
|
|
|
* for IoControlCode values. The content and values of the IOCTLs are
|
|
|
|
* opaque to the protocol. On the server side, the data contained in
|
|
|
|
* an IOCTL is simply packaged and sent to the client side. For
|
|
|
|
* maximum compatibility between the different versions of the Windows
|
|
|
|
* operating system, the client implementation only singles out
|
|
|
|
* critical IOCTLs and invokes the applicable Win32 port API. The
|
|
|
|
* other IOCTLS are passed directly to the client-side driver, and the
|
|
|
|
* processing of this value depends on the drivers installed on the
|
|
|
|
* client side. The values and parameters for these IOCTLS can be
|
|
|
|
* found in [MSFT-W2KDDK] Volume 2, Part 2—Serial and Parallel
|
|
|
|
* Drivers, and in [MSDN-PORTS].
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2014-05-14 18:29:10 +04:00
|
|
|
const char* _comm_serial_ioctl_name(ULONG number)
|
|
|
|
{
|
|
|
|
int i;
|
2014-07-02 17:59:16 +04:00
|
|
|
|
2014-05-14 18:29:10 +04:00
|
|
|
for (i=0; _SERIAL_IOCTL_NAMES[i].number != 0; i++)
|
|
|
|
{
|
|
|
|
if (_SERIAL_IOCTL_NAMES[i].number == number)
|
|
|
|
{
|
|
|
|
return _SERIAL_IOCTL_NAMES[i].name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "(unknown ioctl name)";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-19 18:53:57 +04:00
|
|
|
static BOOL _CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
|
2014-06-16 21:18:45 +04:00
|
|
|
LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
|
2014-04-25 02:20:48 +04:00
|
|
|
{
|
|
|
|
WINPR_COMM* pComm = (WINPR_COMM*) hDevice;
|
2014-06-18 17:58:08 +04:00
|
|
|
SERIAL_DRIVER* pServerSerialDriver = NULL;
|
2014-04-25 02:20:48 +04:00
|
|
|
|
2014-05-14 18:29:10 +04:00
|
|
|
/* clear any previous last error */
|
|
|
|
SetLastError(ERROR_SUCCESS);
|
|
|
|
|
2014-05-12 19:33:56 +04:00
|
|
|
if (hDevice == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-04-25 02:20:48 +04:00
|
|
|
if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd )
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lpOverlapped != NULL)
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_NOT_SUPPORTED);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lpBytesReturned == NULL)
|
|
|
|
{
|
2014-05-12 19:33:56 +04:00
|
|
|
SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
|
2014-04-25 02:20:48 +04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
*lpBytesReturned = 0; /* will be ajusted if required ... */
|
2014-04-25 02:20:48 +04:00
|
|
|
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_DEBUG, "CommDeviceIoControl: IoControlCode: 0x%0.8x", dwIoControlCode);
|
2014-05-12 19:33:56 +04:00
|
|
|
|
2014-06-16 21:18:45 +04:00
|
|
|
/* remoteSerialDriver to be use ...
|
2014-04-28 21:57:17 +04:00
|
|
|
*
|
|
|
|
* FIXME: might prefer to use an automatic rather than static structure
|
|
|
|
*/
|
2014-06-18 17:58:08 +04:00
|
|
|
switch (pComm->serverSerialDriverId)
|
2014-04-25 02:20:48 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
case SerialDriverSerialSys:
|
|
|
|
pServerSerialDriver = SerialSys_s();
|
2014-04-25 02:20:48 +04:00
|
|
|
break;
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
case SerialDriverSerCxSys:
|
|
|
|
pServerSerialDriver = SerCxSys_s();
|
2014-04-25 02:20:48 +04:00
|
|
|
break;
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
case SerialDriverSerCx2Sys:
|
|
|
|
pServerSerialDriver = SerCx2Sys_s();
|
2014-04-25 02:20:48 +04:00
|
|
|
break;
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
case SerialDriverUnknown:
|
2014-04-25 02:20:48 +04:00
|
|
|
default:
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_DEBUG, "Unknown remote serial driver (%d), using SerCx2.sys", pComm->serverSerialDriverId);
|
2014-06-18 17:58:08 +04:00
|
|
|
pServerSerialDriver = SerCx2Sys_s();
|
2014-04-25 02:20:48 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
assert(pServerSerialDriver != NULL);
|
2014-04-25 02:20:48 +04:00
|
|
|
|
|
|
|
switch (dwIoControlCode)
|
|
|
|
{
|
2014-05-26 18:31:56 +04:00
|
|
|
case IOCTL_USBPRINT_GET_1284_ID:
|
|
|
|
{
|
|
|
|
/* FIXME: http://msdn.microsoft.com/en-us/library/windows/hardware/ff551803(v=vs.85).aspx */
|
2014-05-19 18:53:57 +04:00
|
|
|
*lpBytesReturned = nOutBufferSize; /* an empty OutputBuffer will be returned */
|
|
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
2014-05-26 18:31:56 +04:00
|
|
|
return FALSE;
|
|
|
|
}
|
2014-04-25 02:20:48 +04:00
|
|
|
case IOCTL_SERIAL_SET_BAUD_RATE:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_baud_rate)
|
2014-04-25 02:20:48 +04:00
|
|
|
{
|
2014-04-28 21:57:17 +04:00
|
|
|
SERIAL_BAUD_RATE *pBaudRate = (SERIAL_BAUD_RATE*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(SERIAL_BAUD_RATE));
|
|
|
|
if (nInBufferSize < sizeof(SERIAL_BAUD_RATE))
|
|
|
|
{
|
2014-05-12 19:33:56 +04:00
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
2014-04-28 21:57:17 +04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_baud_rate(pComm, pBaudRate);
|
2014-04-25 02:20:48 +04:00
|
|
|
}
|
2014-05-06 18:08:58 +04:00
|
|
|
break;
|
2014-04-28 21:57:17 +04:00
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_BAUD_RATE:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_baud_rate)
|
2014-04-25 02:20:48 +04:00
|
|
|
{
|
2014-04-28 21:57:17 +04:00
|
|
|
SERIAL_BAUD_RATE *pBaudRate = (SERIAL_BAUD_RATE*)lpOutBuffer;
|
|
|
|
|
|
|
|
assert(nOutBufferSize >= sizeof(SERIAL_BAUD_RATE));
|
|
|
|
if (nOutBufferSize < sizeof(SERIAL_BAUD_RATE))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_baud_rate(pComm, pBaudRate))
|
2014-04-28 21:57:17 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(SERIAL_BAUD_RATE);
|
|
|
|
return TRUE;
|
2014-04-25 02:20:48 +04:00
|
|
|
}
|
2014-05-06 18:08:58 +04:00
|
|
|
break;
|
2014-04-28 21:57:17 +04:00
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_PROPERTIES:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_properties)
|
2014-04-25 02:20:48 +04:00
|
|
|
{
|
2014-04-28 21:57:17 +04:00
|
|
|
COMMPROP *pProperties = (COMMPROP*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(COMMPROP));
|
|
|
|
if (nOutBufferSize < sizeof(COMMPROP))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-04-28 21:57:17 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_properties(pComm, pProperties))
|
2014-04-28 21:57:17 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(COMMPROP);
|
|
|
|
return TRUE;
|
2014-04-25 02:20:48 +04:00
|
|
|
}
|
2014-05-06 18:08:58 +04:00
|
|
|
break;
|
2014-04-25 02:20:48 +04:00
|
|
|
}
|
2014-04-29 06:04:09 +04:00
|
|
|
case IOCTL_SERIAL_SET_CHARS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_serial_chars)
|
2014-04-29 06:04:09 +04:00
|
|
|
{
|
|
|
|
SERIAL_CHARS *pSerialChars = (SERIAL_CHARS*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(SERIAL_CHARS));
|
|
|
|
if (nInBufferSize < sizeof(SERIAL_CHARS))
|
|
|
|
{
|
2014-05-12 19:33:56 +04:00
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
2014-04-29 06:04:09 +04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_serial_chars(pComm, pSerialChars);
|
2014-04-29 06:04:09 +04:00
|
|
|
}
|
2014-05-06 18:08:58 +04:00
|
|
|
break;
|
2014-04-29 06:04:09 +04:00
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_CHARS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_serial_chars)
|
2014-04-29 06:04:09 +04:00
|
|
|
{
|
|
|
|
SERIAL_CHARS *pSerialChars = (SERIAL_CHARS*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-04-29 06:04:09 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(SERIAL_CHARS));
|
|
|
|
if (nOutBufferSize < sizeof(SERIAL_CHARS))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-04-29 06:04:09 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_serial_chars(pComm, pSerialChars))
|
2014-04-29 06:04:09 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(SERIAL_CHARS);
|
|
|
|
return TRUE;
|
|
|
|
}
|
2014-05-06 18:08:58 +04:00
|
|
|
break;
|
2014-04-29 06:04:09 +04:00
|
|
|
}
|
2014-04-30 00:25:07 +04:00
|
|
|
case IOCTL_SERIAL_SET_LINE_CONTROL:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_line_control)
|
2014-04-30 00:25:07 +04:00
|
|
|
{
|
|
|
|
SERIAL_LINE_CONTROL *pLineControl = (SERIAL_LINE_CONTROL*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(SERIAL_LINE_CONTROL));
|
|
|
|
if (nInBufferSize < sizeof(SERIAL_LINE_CONTROL))
|
|
|
|
{
|
2014-05-12 19:33:56 +04:00
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
2014-04-30 00:25:07 +04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_line_control(pComm, pLineControl);
|
2014-04-30 00:25:07 +04:00
|
|
|
}
|
2014-05-06 18:08:58 +04:00
|
|
|
break;
|
2014-04-30 00:25:07 +04:00
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_LINE_CONTROL:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_line_control)
|
2014-04-30 00:25:07 +04:00
|
|
|
{
|
|
|
|
SERIAL_LINE_CONTROL *pLineControl = (SERIAL_LINE_CONTROL*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-04-30 00:25:07 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(SERIAL_LINE_CONTROL));
|
|
|
|
if (nOutBufferSize < sizeof(SERIAL_LINE_CONTROL))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-04-30 00:25:07 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_line_control(pComm, pLineControl))
|
2014-04-30 00:25:07 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(SERIAL_LINE_CONTROL);
|
|
|
|
return TRUE;
|
|
|
|
}
|
2014-05-06 18:08:58 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_SET_HANDFLOW:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_handflow)
|
2014-05-06 18:08:58 +04:00
|
|
|
{
|
|
|
|
SERIAL_HANDFLOW *pHandflow = (SERIAL_HANDFLOW*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(SERIAL_HANDFLOW));
|
|
|
|
if (nInBufferSize < sizeof(SERIAL_HANDFLOW))
|
|
|
|
{
|
2014-05-12 19:33:56 +04:00
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
2014-05-06 18:08:58 +04:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_handflow(pComm, pHandflow);
|
2014-05-06 18:08:58 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_HANDFLOW:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_handflow)
|
2014-05-06 18:08:58 +04:00
|
|
|
{
|
|
|
|
SERIAL_HANDFLOW *pHandflow = (SERIAL_HANDFLOW*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-06 18:08:58 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(SERIAL_HANDFLOW));
|
|
|
|
if (nOutBufferSize < sizeof(SERIAL_HANDFLOW))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-05-06 18:08:58 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_handflow(pComm, pHandflow))
|
2014-05-06 18:08:58 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(SERIAL_HANDFLOW);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
2014-04-30 00:25:07 +04:00
|
|
|
}
|
2014-05-12 19:33:56 +04:00
|
|
|
case IOCTL_SERIAL_SET_TIMEOUTS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_timeouts)
|
2014-05-12 19:33:56 +04:00
|
|
|
{
|
|
|
|
SERIAL_TIMEOUTS *pHandflow = (SERIAL_TIMEOUTS*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(SERIAL_TIMEOUTS));
|
|
|
|
if (nInBufferSize < sizeof(SERIAL_TIMEOUTS))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_timeouts(pComm, pHandflow);
|
2014-05-12 19:33:56 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_TIMEOUTS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_timeouts)
|
2014-05-12 19:33:56 +04:00
|
|
|
{
|
|
|
|
SERIAL_TIMEOUTS *pHandflow = (SERIAL_TIMEOUTS*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-12 19:33:56 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(SERIAL_TIMEOUTS));
|
|
|
|
if (nOutBufferSize < sizeof(SERIAL_TIMEOUTS))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-05-12 19:33:56 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_timeouts(pComm, pHandflow))
|
2014-05-12 19:33:56 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(SERIAL_TIMEOUTS);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-12 22:16:13 +04:00
|
|
|
case IOCTL_SERIAL_SET_DTR:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_dtr)
|
2014-05-12 22:16:13 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_dtr(pComm);
|
2014-05-12 22:16:13 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_CLR_DTR:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->clear_dtr)
|
2014-05-12 22:16:13 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->clear_dtr(pComm);
|
2014-05-12 22:16:13 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-12 22:33:33 +04:00
|
|
|
case IOCTL_SERIAL_SET_RTS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_rts)
|
2014-05-12 22:33:33 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_rts(pComm);
|
2014-05-12 22:33:33 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_CLR_RTS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->clear_rts)
|
2014-05-12 22:33:33 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->clear_rts(pComm);
|
2014-05-12 22:33:33 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-13 19:27:51 +04:00
|
|
|
case IOCTL_SERIAL_GET_MODEMSTATUS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_modemstatus)
|
2014-05-13 19:27:51 +04:00
|
|
|
{
|
|
|
|
ULONG *pRegister = (ULONG*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-13 19:27:51 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(ULONG));
|
|
|
|
if (nOutBufferSize < sizeof(ULONG))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-05-13 19:27:51 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_modemstatus(pComm, pRegister))
|
2014-05-13 19:27:51 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(ULONG);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-14 18:29:10 +04:00
|
|
|
case IOCTL_SERIAL_SET_WAIT_MASK:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_wait_mask)
|
2014-05-14 18:29:10 +04:00
|
|
|
{
|
|
|
|
ULONG *pWaitMask = (ULONG*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(ULONG));
|
|
|
|
if (nInBufferSize < sizeof(ULONG))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_wait_mask(pComm, pWaitMask);
|
2014-05-14 18:29:10 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_GET_WAIT_MASK:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_wait_mask)
|
2014-05-14 18:29:10 +04:00
|
|
|
{
|
|
|
|
ULONG *pWaitMask = (ULONG*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-14 18:29:10 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(ULONG));
|
|
|
|
if (nOutBufferSize < sizeof(ULONG))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-05-14 18:29:10 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_wait_mask(pComm, pWaitMask))
|
2014-05-14 18:29:10 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(ULONG);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_WAIT_ON_MASK:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->wait_on_mask)
|
2014-05-14 18:29:10 +04:00
|
|
|
{
|
|
|
|
ULONG *pOutputMask = (ULONG*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-14 18:29:10 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(ULONG));
|
|
|
|
if (nOutBufferSize < sizeof(ULONG))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-05-14 18:29:10 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->wait_on_mask(pComm, pOutputMask))
|
2014-05-19 18:53:57 +04:00
|
|
|
{
|
2014-06-19 21:07:45 +04:00
|
|
|
*lpBytesReturned = sizeof(ULONG);
|
2014-05-14 18:29:10 +04:00
|
|
|
return FALSE;
|
2014-05-19 18:53:57 +04:00
|
|
|
}
|
2014-05-14 18:29:10 +04:00
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(ULONG);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-14 19:30:29 +04:00
|
|
|
case IOCTL_SERIAL_SET_QUEUE_SIZE:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_queue_size)
|
2014-05-14 19:30:29 +04:00
|
|
|
{
|
|
|
|
SERIAL_QUEUE_SIZE *pQueueSize = (SERIAL_QUEUE_SIZE*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(SERIAL_QUEUE_SIZE));
|
|
|
|
if (nInBufferSize < sizeof(SERIAL_QUEUE_SIZE))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_queue_size(pComm, pQueueSize);
|
2014-05-14 19:30:29 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-14 23:21:31 +04:00
|
|
|
case IOCTL_SERIAL_PURGE:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->purge)
|
2014-05-14 23:21:31 +04:00
|
|
|
{
|
|
|
|
ULONG *pPurgeMask = (ULONG*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(ULONG));
|
|
|
|
if (nInBufferSize < sizeof(ULONG))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->purge(pComm, pPurgeMask);
|
2014-05-14 23:21:31 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-23 14:27:09 +04:00
|
|
|
case IOCTL_SERIAL_GET_COMMSTATUS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_commstatus)
|
2014-05-23 14:27:09 +04:00
|
|
|
{
|
|
|
|
SERIAL_STATUS *pCommstatus = (SERIAL_STATUS*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-23 14:27:09 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(SERIAL_STATUS));
|
|
|
|
if (nOutBufferSize < sizeof(SERIAL_STATUS))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-05-23 14:27:09 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_commstatus(pComm, pCommstatus))
|
2014-05-23 14:27:09 +04:00
|
|
|
return FALSE;
|
2014-05-14 19:30:29 +04:00
|
|
|
|
2014-05-23 14:27:09 +04:00
|
|
|
*lpBytesReturned = sizeof(SERIAL_STATUS);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_SET_BREAK_ON:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_break_on)
|
2014-05-23 14:27:09 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_break_on(pComm);
|
2014-05-23 14:27:09 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_SET_BREAK_OFF:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_break_off)
|
2014-05-23 14:27:09 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_break_off(pComm);
|
2014-05-23 14:27:09 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-23 15:04:43 +04:00
|
|
|
case IOCTL_SERIAL_SET_XOFF:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_xoff)
|
2014-05-23 15:04:43 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_xoff(pComm);
|
2014-05-23 15:04:43 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case IOCTL_SERIAL_SET_XON:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->set_xon)
|
2014-05-23 15:04:43 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->set_xon(pComm);
|
2014-05-23 15:04:43 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-05-28 13:39:10 +04:00
|
|
|
case IOCTL_SERIAL_GET_DTRRTS:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->get_dtrrts)
|
2014-05-28 13:39:10 +04:00
|
|
|
{
|
|
|
|
ULONG *pMask = (ULONG*)lpOutBuffer;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-28 13:39:10 +04:00
|
|
|
assert(nOutBufferSize >= sizeof(ULONG));
|
|
|
|
if (nOutBufferSize < sizeof(ULONG))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
2014-06-16 21:18:45 +04:00
|
|
|
return FALSE;
|
2014-05-28 13:39:10 +04:00
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->get_dtrrts(pComm, pMask))
|
2014-05-28 13:39:10 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(ULONG);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-05-28 13:39:10 +04:00
|
|
|
}
|
2014-06-17 17:19:16 +04:00
|
|
|
case IOCTL_SERIAL_CONFIG_SIZE:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->config_size)
|
2014-06-17 17:19:16 +04:00
|
|
|
{
|
|
|
|
ULONG *pSize = (ULONG*)lpOutBuffer;
|
|
|
|
|
|
|
|
assert(nOutBufferSize >= sizeof(ULONG));
|
|
|
|
if (nOutBufferSize < sizeof(ULONG))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
if (!pServerSerialDriver->config_size(pComm, pSize))
|
2014-06-17 17:19:16 +04:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*lpBytesReturned = sizeof(ULONG);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
2014-06-17 18:34:20 +04:00
|
|
|
case IOCTL_SERIAL_IMMEDIATE_CHAR:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->immediate_char)
|
2014-06-17 18:34:20 +04:00
|
|
|
{
|
|
|
|
UCHAR *pChar = (UCHAR*)lpInBuffer;
|
|
|
|
|
|
|
|
assert(nInBufferSize >= sizeof(UCHAR));
|
|
|
|
if (nInBufferSize < sizeof(UCHAR))
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->immediate_char(pComm, pChar);
|
2014-06-17 18:34:20 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-06-17 19:49:06 +04:00
|
|
|
case IOCTL_SERIAL_RESET_DEVICE:
|
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
if (pServerSerialDriver->reset_device)
|
2014-06-17 19:49:06 +04:00
|
|
|
{
|
2014-06-18 17:58:08 +04:00
|
|
|
return pServerSerialDriver->reset_device(pComm);
|
2014-06-17 19:49:06 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-04-25 02:20:48 +04:00
|
|
|
}
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, _T("unsupported IoControlCode=[0x%lX] %s (remote serial driver: %s)"),
|
2014-06-18 17:58:08 +04:00
|
|
|
dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), pServerSerialDriver->name);
|
2014-06-17 19:49:06 +04:00
|
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED); /* => STATUS_NOT_IMPLEMENTED */
|
2014-04-28 21:57:17 +04:00
|
|
|
return FALSE;
|
2014-06-16 21:18:45 +04:00
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-19 18:53:57 +04:00
|
|
|
/**
|
|
|
|
* FIXME: to be used through winpr-io's DeviceIoControl
|
|
|
|
*
|
|
|
|
* Any previous error as returned by GetLastError is cleared.
|
|
|
|
*
|
|
|
|
* ERRORS:
|
|
|
|
* ERROR_INVALID_HANDLE
|
|
|
|
* ERROR_INVALID_PARAMETER
|
|
|
|
* ERROR_NOT_SUPPORTED lpOverlapped is not supported
|
|
|
|
* ERROR_INSUFFICIENT_BUFFER
|
|
|
|
* ERROR_CALL_NOT_IMPLEMENTED unimplemented ioctl
|
|
|
|
*/
|
|
|
|
BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize,
|
2014-06-16 21:18:45 +04:00
|
|
|
LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
|
2014-05-19 18:53:57 +04:00
|
|
|
{
|
|
|
|
WINPR_COMM* pComm = (WINPR_COMM*) hDevice;
|
|
|
|
BOOL result;
|
|
|
|
|
|
|
|
if (hDevice == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd )
|
|
|
|
{
|
|
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = _CommDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize,
|
2014-06-16 21:18:45 +04:00
|
|
|
lpOutBuffer, nOutBufferSize, lpBytesReturned, lpOverlapped);
|
2014-05-19 18:53:57 +04:00
|
|
|
|
2014-06-19 21:07:45 +04:00
|
|
|
if (lpBytesReturned && *lpBytesReturned != nOutBufferSize)
|
|
|
|
{
|
|
|
|
/* This might be a hint for a bug, especially when result==TRUE */
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, "lpBytesReturned=%ld and nOutBufferSize=%ld are different!", *lpBytesReturned, nOutBufferSize);
|
2014-06-19 21:07:45 +04:00
|
|
|
}
|
|
|
|
|
2014-05-19 18:53:57 +04:00
|
|
|
if (pComm->permissive)
|
|
|
|
{
|
|
|
|
if (!result)
|
|
|
|
{
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, "[permissive]: whereas it failed, made to succeed IoControlCode=[0x%lX] %s, last-error: 0x%lX",
|
2014-06-16 21:18:45 +04:00
|
|
|
dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), GetLastError());
|
2014-05-19 18:53:57 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE; /* always! */
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios *termios_p)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
struct termios currentState;
|
2014-04-25 02:20:48 +04:00
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
|
|
|
|
{
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, "tcsetattr failure, errno: %d", errno);
|
2014-04-28 21:57:17 +04:00
|
|
|
return result;
|
|
|
|
}
|
2014-04-25 02:20:48 +04:00
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
/* NB: tcsetattr() can succeed even if not all changes have been applied. */
|
|
|
|
ZeroMemory(¤tState, sizeof(struct termios));
|
|
|
|
if ((result = tcgetattr(fd, ¤tState)) < 0)
|
|
|
|
{
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
|
2014-04-28 21:57:17 +04:00
|
|
|
return result;
|
2014-04-25 02:20:48 +04:00
|
|
|
}
|
|
|
|
|
2014-04-28 21:57:17 +04:00
|
|
|
if (memcmp(¤tState, &termios_p, sizeof(struct termios)) != 0)
|
|
|
|
{
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_DEBUG, "all termios parameters are not set yet, doing a second attempt...");
|
2014-04-28 21:57:17 +04:00
|
|
|
if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
|
|
|
|
{
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, "2nd tcsetattr failure, errno: %d", errno);
|
2014-04-28 21:57:17 +04:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
ZeroMemory(¤tState, sizeof(struct termios));
|
|
|
|
if ((result = tcgetattr(fd, ¤tState)) < 0)
|
|
|
|
{
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
|
2014-04-28 21:57:17 +04:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (memcmp(¤tState, termios_p, sizeof(struct termios)) != 0)
|
|
|
|
{
|
2014-07-02 17:59:16 +04:00
|
|
|
CommLog_Print(WLOG_WARN, "Failure: all termios parameters are still not set on a second attempt");
|
2014-06-19 21:07:45 +04:00
|
|
|
return -1;
|
2014-04-28 21:57:17 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-04-25 02:20:48 +04:00
|
|
|
#endif /* _WIN32 */
|