diff options
Diffstat (limited to 'winpr/libwinpr/comm/comm_ioctl.c')
-rw-r--r-- | winpr/libwinpr/comm/comm_ioctl.c | 731 |
1 files changed, 731 insertions, 0 deletions
diff --git a/winpr/libwinpr/comm/comm_ioctl.c b/winpr/libwinpr/comm/comm_ioctl.c new file mode 100644 index 0000000..4f208ef --- /dev/null +++ b/winpr/libwinpr/comm/comm_ioctl.c @@ -0,0 +1,731 @@ +/** + * 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. + */ + +#include <winpr/config.h> + +#if defined __linux__ && !defined ANDROID + +#include <winpr/assert.h> +#include <errno.h> + +#include <winpr/wlog.h> + +#include "comm.h" +#include "comm_ioctl.h" +#include "comm_serial_sys.h" +#include "comm_sercx_sys.h" +#include "comm_sercx2_sys.h" + +/* 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]. + */ + +const char* _comm_serial_ioctl_name(ULONG number) +{ + for (int 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)"; +} + +static BOOL s_CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, + DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, + LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hDevice; + SERIAL_DRIVER* pServerSerialDriver = NULL; + + if (!CommIsHandleValid(hDevice)) + return FALSE; + + if (lpOverlapped) + { + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; + } + + if (lpBytesReturned == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */ + return FALSE; + } + + /* clear any previous last error */ + SetLastError(ERROR_SUCCESS); + + *lpBytesReturned = 0; /* will be ajusted if required ... */ + + CommLog_Print(WLOG_DEBUG, "CommDeviceIoControl: IoControlCode: 0x%0.8x", dwIoControlCode); + + /* remoteSerialDriver to be use ... + * + * FIXME: might prefer to use an automatic rather than static structure + */ + switch (pComm->serverSerialDriverId) + { + case SerialDriverSerialSys: + pServerSerialDriver = SerialSys_s(); + break; + + case SerialDriverSerCxSys: + pServerSerialDriver = SerCxSys_s(); + break; + + case SerialDriverSerCx2Sys: + pServerSerialDriver = SerCx2Sys_s(); + break; + + case SerialDriverUnknown: + default: + CommLog_Print(WLOG_DEBUG, "Unknown remote serial driver (%d), using SerCx2.sys", + pComm->serverSerialDriverId); + pServerSerialDriver = SerCx2Sys_s(); + break; + } + + WINPR_ASSERT(pServerSerialDriver != NULL); + + switch (dwIoControlCode) + { + case IOCTL_USBPRINT_GET_1284_ID: + { + /* FIXME: + * http://msdn.microsoft.com/en-us/library/windows/hardware/ff551803(v=vs.85).aspx */ + *lpBytesReturned = nOutBufferSize; /* an empty OutputBuffer will be returned */ + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + case IOCTL_SERIAL_SET_BAUD_RATE: + { + if (pServerSerialDriver->set_baud_rate) + { + SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_BAUD_RATE)); + if (nInBufferSize < sizeof(SERIAL_BAUD_RATE)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->set_baud_rate(pComm, pBaudRate); + } + break; + } + case IOCTL_SERIAL_GET_BAUD_RATE: + { + if (pServerSerialDriver->get_baud_rate) + { + SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_BAUD_RATE)); + if (nOutBufferSize < sizeof(SERIAL_BAUD_RATE)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_baud_rate(pComm, pBaudRate)) + return FALSE; + + *lpBytesReturned = sizeof(SERIAL_BAUD_RATE); + return TRUE; + } + break; + } + case IOCTL_SERIAL_GET_PROPERTIES: + { + if (pServerSerialDriver->get_properties) + { + COMMPROP* pProperties = (COMMPROP*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(COMMPROP)); + if (nOutBufferSize < sizeof(COMMPROP)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_properties(pComm, pProperties)) + return FALSE; + + *lpBytesReturned = sizeof(COMMPROP); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_CHARS: + { + if (pServerSerialDriver->set_serial_chars) + { + SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_CHARS)); + if (nInBufferSize < sizeof(SERIAL_CHARS)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->set_serial_chars(pComm, pSerialChars); + } + break; + } + case IOCTL_SERIAL_GET_CHARS: + { + if (pServerSerialDriver->get_serial_chars) + { + SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_CHARS)); + if (nOutBufferSize < sizeof(SERIAL_CHARS)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_serial_chars(pComm, pSerialChars)) + return FALSE; + + *lpBytesReturned = sizeof(SERIAL_CHARS); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_LINE_CONTROL: + { + if (pServerSerialDriver->set_line_control) + { + SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_LINE_CONTROL)); + if (nInBufferSize < sizeof(SERIAL_LINE_CONTROL)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->set_line_control(pComm, pLineControl); + } + break; + } + case IOCTL_SERIAL_GET_LINE_CONTROL: + { + if (pServerSerialDriver->get_line_control) + { + SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_LINE_CONTROL)); + if (nOutBufferSize < sizeof(SERIAL_LINE_CONTROL)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_line_control(pComm, pLineControl)) + return FALSE; + + *lpBytesReturned = sizeof(SERIAL_LINE_CONTROL); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_HANDFLOW: + { + if (pServerSerialDriver->set_handflow) + { + SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_HANDFLOW)); + if (nInBufferSize < sizeof(SERIAL_HANDFLOW)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->set_handflow(pComm, pHandflow); + } + break; + } + case IOCTL_SERIAL_GET_HANDFLOW: + { + if (pServerSerialDriver->get_handflow) + { + SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_HANDFLOW)); + if (nOutBufferSize < sizeof(SERIAL_HANDFLOW)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_handflow(pComm, pHandflow)) + return FALSE; + + *lpBytesReturned = sizeof(SERIAL_HANDFLOW); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_TIMEOUTS: + { + if (pServerSerialDriver->set_timeouts) + { + SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_TIMEOUTS)); + if (nInBufferSize < sizeof(SERIAL_TIMEOUTS)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->set_timeouts(pComm, pHandflow); + } + break; + } + case IOCTL_SERIAL_GET_TIMEOUTS: + { + if (pServerSerialDriver->get_timeouts) + { + SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_TIMEOUTS)); + if (nOutBufferSize < sizeof(SERIAL_TIMEOUTS)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_timeouts(pComm, pHandflow)) + return FALSE; + + *lpBytesReturned = sizeof(SERIAL_TIMEOUTS); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_DTR: + { + if (pServerSerialDriver->set_dtr) + { + return pServerSerialDriver->set_dtr(pComm); + } + break; + } + case IOCTL_SERIAL_CLR_DTR: + { + if (pServerSerialDriver->clear_dtr) + { + return pServerSerialDriver->clear_dtr(pComm); + } + break; + } + case IOCTL_SERIAL_SET_RTS: + { + if (pServerSerialDriver->set_rts) + { + return pServerSerialDriver->set_rts(pComm); + } + break; + } + case IOCTL_SERIAL_CLR_RTS: + { + if (pServerSerialDriver->clear_rts) + { + return pServerSerialDriver->clear_rts(pComm); + } + break; + } + case IOCTL_SERIAL_GET_MODEMSTATUS: + { + if (pServerSerialDriver->get_modemstatus) + { + ULONG* pRegister = (ULONG*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG)); + if (nOutBufferSize < sizeof(ULONG)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_modemstatus(pComm, pRegister)) + return FALSE; + + *lpBytesReturned = sizeof(ULONG); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_WAIT_MASK: + { + if (pServerSerialDriver->set_wait_mask) + { + ULONG* pWaitMask = (ULONG*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(ULONG)); + if (nInBufferSize < sizeof(ULONG)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->set_wait_mask(pComm, pWaitMask); + } + break; + } + case IOCTL_SERIAL_GET_WAIT_MASK: + { + if (pServerSerialDriver->get_wait_mask) + { + ULONG* pWaitMask = (ULONG*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG)); + if (nOutBufferSize < sizeof(ULONG)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_wait_mask(pComm, pWaitMask)) + return FALSE; + + *lpBytesReturned = sizeof(ULONG); + return TRUE; + } + break; + } + case IOCTL_SERIAL_WAIT_ON_MASK: + { + if (pServerSerialDriver->wait_on_mask) + { + ULONG* pOutputMask = (ULONG*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG)); + if (nOutBufferSize < sizeof(ULONG)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->wait_on_mask(pComm, pOutputMask)) + { + *lpBytesReturned = sizeof(ULONG); + return FALSE; + } + + *lpBytesReturned = sizeof(ULONG); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_QUEUE_SIZE: + { + if (pServerSerialDriver->set_queue_size) + { + SERIAL_QUEUE_SIZE* pQueueSize = (SERIAL_QUEUE_SIZE*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_QUEUE_SIZE)); + if (nInBufferSize < sizeof(SERIAL_QUEUE_SIZE)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->set_queue_size(pComm, pQueueSize); + } + break; + } + case IOCTL_SERIAL_PURGE: + { + if (pServerSerialDriver->purge) + { + ULONG* pPurgeMask = (ULONG*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(ULONG)); + if (nInBufferSize < sizeof(ULONG)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->purge(pComm, pPurgeMask); + } + break; + } + case IOCTL_SERIAL_GET_COMMSTATUS: + { + if (pServerSerialDriver->get_commstatus) + { + SERIAL_STATUS* pCommstatus = (SERIAL_STATUS*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_STATUS)); + if (nOutBufferSize < sizeof(SERIAL_STATUS)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_commstatus(pComm, pCommstatus)) + return FALSE; + + *lpBytesReturned = sizeof(SERIAL_STATUS); + return TRUE; + } + break; + } + case IOCTL_SERIAL_SET_BREAK_ON: + { + if (pServerSerialDriver->set_break_on) + { + return pServerSerialDriver->set_break_on(pComm); + } + break; + } + case IOCTL_SERIAL_SET_BREAK_OFF: + { + if (pServerSerialDriver->set_break_off) + { + return pServerSerialDriver->set_break_off(pComm); + } + break; + } + case IOCTL_SERIAL_SET_XOFF: + { + if (pServerSerialDriver->set_xoff) + { + return pServerSerialDriver->set_xoff(pComm); + } + break; + } + case IOCTL_SERIAL_SET_XON: + { + if (pServerSerialDriver->set_xon) + { + return pServerSerialDriver->set_xon(pComm); + } + break; + } + case IOCTL_SERIAL_GET_DTRRTS: + { + if (pServerSerialDriver->get_dtrrts) + { + ULONG* pMask = (ULONG*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG)); + if (nOutBufferSize < sizeof(ULONG)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->get_dtrrts(pComm, pMask)) + return FALSE; + + *lpBytesReturned = sizeof(ULONG); + return TRUE; + } + break; + } + case IOCTL_SERIAL_CONFIG_SIZE: + { + if (pServerSerialDriver->config_size) + { + ULONG* pSize = (ULONG*)lpOutBuffer; + + WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG)); + if (nOutBufferSize < sizeof(ULONG)) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + if (!pServerSerialDriver->config_size(pComm, pSize)) + return FALSE; + + *lpBytesReturned = sizeof(ULONG); + return TRUE; + } + break; + } + case IOCTL_SERIAL_IMMEDIATE_CHAR: + { + if (pServerSerialDriver->immediate_char) + { + UCHAR* pChar = (UCHAR*)lpInBuffer; + + WINPR_ASSERT(nInBufferSize >= sizeof(UCHAR)); + if (nInBufferSize < sizeof(UCHAR)) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return pServerSerialDriver->immediate_char(pComm, pChar); + } + break; + } + case IOCTL_SERIAL_RESET_DEVICE: + { + if (pServerSerialDriver->reset_device) + { + return pServerSerialDriver->reset_device(pComm); + } + break; + } + } + + CommLog_Print( + WLOG_WARN, _T("unsupported IoControlCode=[0x%08" PRIX32 "] %s (remote serial driver: %s)"), + dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), pServerSerialDriver->name); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); /* => STATUS_NOT_IMPLEMENTED */ + return FALSE; +} + +/** + * 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, LPVOID lpOutBuffer, DWORD nOutBufferSize, + LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hDevice; + BOOL result = 0; + + if (hDevice == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (!CommIsHandled(hDevice)) + return FALSE; + + if (!pComm->fd) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + result = s_CommDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, + nOutBufferSize, lpBytesReturned, lpOverlapped); + + if (lpBytesReturned && *lpBytesReturned != nOutBufferSize) + { + /* This might be a hint for a bug, especially when result==TRUE */ + CommLog_Print(WLOG_WARN, + "lpBytesReturned=%" PRIu32 " and nOutBufferSize=%" PRIu32 " are different!", + *lpBytesReturned, nOutBufferSize); + } + + if (pComm->permissive) + { + if (!result) + { + CommLog_Print( + WLOG_WARN, + "[permissive]: whereas it failed, made to succeed IoControlCode=[0x%08" PRIX32 + "] %s, last-error: 0x%08" PRIX32 "", + dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), GetLastError()); + } + + return TRUE; /* always! */ + } + + return result; +} + +int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios* termios_p) +{ + int result = 0; + struct termios currentState = { 0 }; + + if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0) + { + CommLog_Print(WLOG_WARN, "tcsetattr failure, errno: %d", errno); + return result; + } + + /* NB: tcsetattr() can succeed even if not all changes have been applied. */ + if ((result = tcgetattr(fd, ¤tState)) < 0) + { + CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno); + return result; + } + + if (memcmp(¤tState, termios_p, sizeof(struct termios)) != 0) + { + CommLog_Print(WLOG_DEBUG, + "all termios parameters are not set yet, doing a second attempt..."); + if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0) + { + CommLog_Print(WLOG_WARN, "2nd tcsetattr failure, errno: %d", errno); + return result; + } + + ZeroMemory(¤tState, sizeof(struct termios)); + if ((result = tcgetattr(fd, ¤tState)) < 0) + { + CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno); + return result; + } + + if (memcmp(¤tState, termios_p, sizeof(struct termios)) != 0) + { + CommLog_Print(WLOG_WARN, + "Failure: all termios parameters are still not set on a second attempt"); + return -1; + } + } + + return 0; +} + +#endif /* __linux__ */ |