diff options
Diffstat (limited to '')
-rw-r--r-- | winpr/libwinpr/comm/comm.c | 1426 |
1 files changed, 1426 insertions, 0 deletions
diff --git a/winpr/libwinpr/comm/comm.c b/winpr/libwinpr/comm/comm.c new file mode 100644 index 0000000..c4e828e --- /dev/null +++ b/winpr/libwinpr/comm/comm.c @@ -0,0 +1,1426 @@ +/** + * 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 <fcntl.h> +#include <pthread.h> +#include <stdarg.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> + +#include <winpr/crt.h> +#include <winpr/comm.h> +#include <winpr/tchar.h> +#include <winpr/wlog.h> +#include <winpr/handle.h> + +#include "comm_ioctl.h" + +/** + * Communication Resources: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363196/ + */ + +#include "comm.h" + +static wLog* _Log = NULL; + +struct comm_device +{ + LPTSTR name; + LPTSTR path; +}; + +typedef struct comm_device COMM_DEVICE; + +/* FIXME: get a clever data structure, see also io.h functions */ +/* _CommDevices is a NULL-terminated array with a maximun of COMM_DEVICE_MAX COMM_DEVICE */ +#define COMM_DEVICE_MAX 128 +static COMM_DEVICE** _CommDevices = NULL; +static CRITICAL_SECTION _CommDevicesLock; + +static HANDLE_CREATOR _CommHandleCreator; + +static pthread_once_t _CommInitialized = PTHREAD_ONCE_INIT; + +static int CommGetFd(HANDLE handle) +{ + WINPR_COMM* comm = (WINPR_COMM*)handle; + + if (!CommIsHandled(handle)) + return -1; + + return comm->fd; +} + +HANDLE_CREATOR* GetCommHandleCreator(void) +{ + _CommHandleCreator.IsHandled = IsCommDevice; + _CommHandleCreator.CreateFileA = CommCreateFileA; + return &_CommHandleCreator; +} + +static void _CommInit(void) +{ + /* NB: error management to be done outside of this function */ + WINPR_ASSERT(_Log == NULL); + WINPR_ASSERT(_CommDevices == NULL); + _CommDevices = (COMM_DEVICE**)calloc(COMM_DEVICE_MAX + 1, sizeof(COMM_DEVICE*)); + + if (!_CommDevices) + return; + + if (!InitializeCriticalSectionEx(&_CommDevicesLock, 0, 0)) + { + free(_CommDevices); + _CommDevices = NULL; + return; + } + + _Log = WLog_Get("com.winpr.comm"); + WINPR_ASSERT(_Log != NULL); +} + +/** + * Returns TRUE when the comm module is correctly intialized, FALSE otherwise + * with ERROR_DLL_INIT_FAILED set as the last error. + */ +static BOOL CommInitialized(void) +{ + if (pthread_once(&_CommInitialized, _CommInit) != 0) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + return TRUE; +} + +void CommLog_Print(DWORD level, ...) +{ + if (!CommInitialized()) + return; + + va_list ap; + va_start(ap, level); + WLog_PrintVA(_Log, level, ap); + va_end(ap); +} + +BOOL BuildCommDCBA(LPCSTR lpDef, LPDCB lpDCB) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL BuildCommDCBW(LPCWSTR lpDef, LPDCB lpDCB) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL BuildCommDCBAndTimeoutsA(LPCSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL BuildCommDCBAndTimeoutsW(LPCWSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL CommConfigDialogA(LPCSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL CommConfigDialogW(LPCWSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hCommDev; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL SetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, DWORD dwSize) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hCommDev; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL GetCommMask(HANDLE hFile, PDWORD lpEvtMask) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL GetCommModemStatus(HANDLE hFile, PDWORD lpModemStat) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +/** + * ERRORS: + * ERROR_DLL_INIT_FAILED + * ERROR_INVALID_HANDLE + */ +BOOL GetCommProperties(HANDLE hFile, LPCOMMPROP lpCommProp) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + DWORD bytesReturned = 0; + + if (!CommIsHandleValid(hFile)) + return FALSE; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_PROPERTIES, NULL, 0, lpCommProp, + sizeof(COMMPROP), &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "GetCommProperties failure."); + return FALSE; + } + + return TRUE; +} + +/** + * + * + * ERRORS: + * ERROR_INVALID_HANDLE + * ERROR_INVALID_DATA + * ERROR_IO_DEVICE + * ERROR_OUTOFMEMORY + */ +BOOL GetCommState(HANDLE hFile, LPDCB lpDCB) +{ + DCB* lpLocalDcb = NULL; + struct termios currentState; + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + DWORD bytesReturned = 0; + + if (!CommIsHandleValid(hFile)) + return FALSE; + + if (!lpDCB) + { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + if (lpDCB->DCBlength < sizeof(DCB)) + { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + if (tcgetattr(pComm->fd, ¤tState) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + lpLocalDcb = (DCB*)calloc(1, lpDCB->DCBlength); + + if (lpLocalDcb == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + return FALSE; + } + + /* error_handle */ + lpLocalDcb->DCBlength = lpDCB->DCBlength; + SERIAL_BAUD_RATE baudRate; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_BAUD_RATE, NULL, 0, &baudRate, + sizeof(SERIAL_BAUD_RATE), &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the baud rate."); + goto error_handle; + } + + lpLocalDcb->BaudRate = baudRate.BaudRate; + lpLocalDcb->fBinary = (currentState.c_cflag & ICANON) == 0; + + if (!lpLocalDcb->fBinary) + { + CommLog_Print(WLOG_WARN, "Unexpected nonbinary mode, consider to unset the ICANON flag."); + } + + lpLocalDcb->fParity = (currentState.c_iflag & INPCK) != 0; + SERIAL_HANDFLOW handflow; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_HANDFLOW, NULL, 0, &handflow, + sizeof(SERIAL_HANDFLOW), &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the handflow settings."); + goto error_handle; + } + + lpLocalDcb->fOutxCtsFlow = (handflow.ControlHandShake & SERIAL_CTS_HANDSHAKE) != 0; + lpLocalDcb->fOutxDsrFlow = (handflow.ControlHandShake & SERIAL_DSR_HANDSHAKE) != 0; + + if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) + { + lpLocalDcb->fDtrControl = DTR_CONTROL_HANDSHAKE; + } + else if (handflow.ControlHandShake & SERIAL_DTR_CONTROL) + { + lpLocalDcb->fDtrControl = DTR_CONTROL_ENABLE; + } + else + { + lpLocalDcb->fDtrControl = DTR_CONTROL_DISABLE; + } + + lpLocalDcb->fDsrSensitivity = (handflow.ControlHandShake & SERIAL_DSR_SENSITIVITY) != 0; + lpLocalDcb->fTXContinueOnXoff = (handflow.FlowReplace & SERIAL_XOFF_CONTINUE) != 0; + lpLocalDcb->fOutX = (handflow.FlowReplace & SERIAL_AUTO_TRANSMIT) != 0; + lpLocalDcb->fInX = (handflow.FlowReplace & SERIAL_AUTO_RECEIVE) != 0; + lpLocalDcb->fErrorChar = (handflow.FlowReplace & SERIAL_ERROR_CHAR) != 0; + lpLocalDcb->fNull = (handflow.FlowReplace & SERIAL_NULL_STRIPPING) != 0; + + if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE) + { + lpLocalDcb->fRtsControl = RTS_CONTROL_HANDSHAKE; + } + else if (handflow.FlowReplace & SERIAL_RTS_CONTROL) + { + lpLocalDcb->fRtsControl = RTS_CONTROL_ENABLE; + } + else + { + lpLocalDcb->fRtsControl = RTS_CONTROL_DISABLE; + } + + // FIXME: how to get the RTS_CONTROL_TOGGLE state? Does it match the UART 16750's Autoflow + // Control Enabled bit in its Modem Control Register (MCR) + lpLocalDcb->fAbortOnError = (handflow.ControlHandShake & SERIAL_ERROR_ABORT) != 0; + /* lpLocalDcb->fDummy2 not used */ + lpLocalDcb->wReserved = 0; /* must be zero */ + lpLocalDcb->XonLim = handflow.XonLimit; + lpLocalDcb->XoffLim = handflow.XoffLimit; + SERIAL_LINE_CONTROL lineControl; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_LINE_CONTROL, NULL, 0, &lineControl, + sizeof(SERIAL_LINE_CONTROL), &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the control settings."); + goto error_handle; + } + + lpLocalDcb->ByteSize = lineControl.WordLength; + lpLocalDcb->Parity = lineControl.Parity; + lpLocalDcb->StopBits = lineControl.StopBits; + SERIAL_CHARS serialChars; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, NULL, 0, &serialChars, + sizeof(SERIAL_CHARS), &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the serial chars."); + goto error_handle; + } + + lpLocalDcb->XonChar = serialChars.XonChar; + lpLocalDcb->XoffChar = serialChars.XoffChar; + lpLocalDcb->ErrorChar = serialChars.ErrorChar; + lpLocalDcb->EofChar = serialChars.EofChar; + lpLocalDcb->EvtChar = serialChars.EventChar; + memcpy(lpDCB, lpLocalDcb, lpDCB->DCBlength); + free(lpLocalDcb); + return TRUE; +error_handle: + free(lpLocalDcb); + return FALSE; +} + +/** + * @return TRUE on success, FALSE otherwise. + * + * As of today, SetCommState() can fail half-way with some settings + * applied and some others not. SetCommState() returns on the first + * failure met. FIXME: or is it correct? + * + * ERRORS: + * ERROR_INVALID_HANDLE + * ERROR_IO_DEVICE + */ +BOOL SetCommState(HANDLE hFile, LPDCB lpDCB) +{ + struct termios upcomingTermios = { 0 }; + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + DWORD bytesReturned = 0; + + /* FIXME: validate changes according GetCommProperties? */ + + if (!CommIsHandleValid(hFile)) + return FALSE; + + if (!lpDCB) + { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + /* NB: did the choice to call ioctls first when available and + then to setup upcomingTermios. Don't mix both stages. */ + /** ioctl calls stage **/ + SERIAL_BAUD_RATE baudRate; + baudRate.BaudRate = lpDCB->BaudRate; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_BAUD_RATE, &baudRate, sizeof(SERIAL_BAUD_RATE), + NULL, 0, &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the baud rate."); + return FALSE; + } + + SERIAL_CHARS serialChars; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, NULL, 0, &serialChars, + sizeof(SERIAL_CHARS), &bytesReturned, + NULL)) /* as of today, required for BreakChar */ + { + CommLog_Print(WLOG_WARN, "SetCommState failure: could not get the initial serial chars."); + return FALSE; + } + + serialChars.XonChar = lpDCB->XonChar; + serialChars.XoffChar = lpDCB->XoffChar; + serialChars.ErrorChar = lpDCB->ErrorChar; + serialChars.EofChar = lpDCB->EofChar; + serialChars.EventChar = lpDCB->EvtChar; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_CHARS, &serialChars, sizeof(SERIAL_CHARS), + NULL, 0, &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the serial chars."); + return FALSE; + } + + SERIAL_LINE_CONTROL lineControl; + lineControl.StopBits = lpDCB->StopBits; + lineControl.Parity = lpDCB->Parity; + lineControl.WordLength = lpDCB->ByteSize; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_LINE_CONTROL, &lineControl, + sizeof(SERIAL_LINE_CONTROL), NULL, 0, &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the control settings."); + return FALSE; + } + + SERIAL_HANDFLOW handflow = { 0 }; + + if (lpDCB->fOutxCtsFlow) + { + handflow.ControlHandShake |= SERIAL_CTS_HANDSHAKE; + } + + if (lpDCB->fOutxDsrFlow) + { + handflow.ControlHandShake |= SERIAL_DSR_HANDSHAKE; + } + + switch (lpDCB->fDtrControl) + { + case SERIAL_DTR_HANDSHAKE: + handflow.ControlHandShake |= DTR_CONTROL_HANDSHAKE; + break; + + case SERIAL_DTR_CONTROL: + handflow.ControlHandShake |= DTR_CONTROL_ENABLE; + break; + + case DTR_CONTROL_DISABLE: + /* do nothing since handflow is init-zeroed */ + break; + + default: + CommLog_Print(WLOG_WARN, "Unexpected fDtrControl value: %" PRIu32 "\n", + lpDCB->fDtrControl); + return FALSE; + } + + if (lpDCB->fDsrSensitivity) + { + handflow.ControlHandShake |= SERIAL_DSR_SENSITIVITY; + } + + if (lpDCB->fTXContinueOnXoff) + { + handflow.FlowReplace |= SERIAL_XOFF_CONTINUE; + } + + if (lpDCB->fOutX) + { + handflow.FlowReplace |= SERIAL_AUTO_TRANSMIT; + } + + if (lpDCB->fInX) + { + handflow.FlowReplace |= SERIAL_AUTO_RECEIVE; + } + + if (lpDCB->fErrorChar) + { + handflow.FlowReplace |= SERIAL_ERROR_CHAR; + } + + if (lpDCB->fNull) + { + handflow.FlowReplace |= SERIAL_NULL_STRIPPING; + } + + switch (lpDCB->fRtsControl) + { + case RTS_CONTROL_TOGGLE: + CommLog_Print(WLOG_WARN, "Unsupported RTS_CONTROL_TOGGLE feature"); + // FIXME: see also GetCommState() + return FALSE; + + case RTS_CONTROL_HANDSHAKE: + handflow.FlowReplace |= SERIAL_RTS_HANDSHAKE; + break; + + case RTS_CONTROL_ENABLE: + handflow.FlowReplace |= SERIAL_RTS_CONTROL; + break; + + case RTS_CONTROL_DISABLE: + /* do nothing since handflow is init-zeroed */ + break; + + default: + CommLog_Print(WLOG_WARN, "Unexpected fRtsControl value: %" PRIu32 "\n", + lpDCB->fRtsControl); + return FALSE; + } + + if (lpDCB->fAbortOnError) + { + handflow.ControlHandShake |= SERIAL_ERROR_ABORT; + } + + /* lpDCB->fDummy2 not used */ + /* lpLocalDcb->wReserved ignored */ + handflow.XonLimit = lpDCB->XonLim; + handflow.XoffLimit = lpDCB->XoffLim; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_HANDFLOW, &handflow, sizeof(SERIAL_HANDFLOW), + NULL, 0, &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the handflow settings."); + return FALSE; + } + + /** upcomingTermios stage **/ + + if (tcgetattr(pComm->fd, &upcomingTermios) < + 0) /* NB: preserves current settings not directly handled by the Communication Functions */ + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + if (lpDCB->fBinary) + { + upcomingTermios.c_lflag &= ~ICANON; + } + else + { + upcomingTermios.c_lflag |= ICANON; + CommLog_Print(WLOG_WARN, "Unexpected nonbinary mode, consider to unset the ICANON flag."); + } + + if (lpDCB->fParity) + { + upcomingTermios.c_iflag |= INPCK; + } + else + { + upcomingTermios.c_iflag &= ~INPCK; + } + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363423%28v=vs.85%29.aspx + * + * The SetCommState function reconfigures the communications + * resource, but it does not affect the internal output and + * input buffers of the specified driver. The buffers are not + * flushed, and pending read and write operations are not + * terminated prematurely. + * + * TCSANOW matches the best this definition + */ + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + +/** + * ERRORS: + * ERROR_INVALID_HANDLE + */ +BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + DWORD bytesReturned = 0; + + if (!CommIsHandleValid(hFile)) + return FALSE; + + /* as of today, SERIAL_TIMEOUTS and COMMTIMEOUTS structures are identical */ + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_TIMEOUTS, NULL, 0, lpCommTimeouts, + sizeof(COMMTIMEOUTS), &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "GetCommTimeouts failure."); + return FALSE; + } + + return TRUE; +} + +/** + * ERRORS: + * ERROR_INVALID_HANDLE + */ +BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + DWORD bytesReturned = 0; + + if (!CommIsHandleValid(hFile)) + return FALSE; + + /* as of today, SERIAL_TIMEOUTS and COMMTIMEOUTS structures are identical */ + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_TIMEOUTS, lpCommTimeouts, sizeof(COMMTIMEOUTS), + NULL, 0, &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "SetCommTimeouts failure."); + return FALSE; + } + + return TRUE; +} + +BOOL GetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL GetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL SetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL SetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize) +{ + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL SetCommBreak(HANDLE hFile) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL ClearCommBreak(HANDLE hFile) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL ClearCommError(HANDLE hFile, PDWORD lpErrors, LPCOMSTAT lpStat) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + DWORD bytesReturned = 0; + + if (!CommIsHandleValid(hFile)) + return FALSE; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_PURGE, &dwFlags, sizeof(DWORD), NULL, 0, + &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "PurgeComm failure."); + return FALSE; + } + + return TRUE; +} + +BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + SERIAL_QUEUE_SIZE queueSize; + DWORD bytesReturned = 0; + + if (!CommIsHandleValid(hFile)) + return FALSE; + + queueSize.InSize = dwInQueue; + queueSize.OutSize = dwOutQueue; + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_QUEUE_SIZE, &queueSize, + sizeof(SERIAL_QUEUE_SIZE), NULL, 0, &bytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "SetCommTimeouts failure."); + return FALSE; + } + + return TRUE; +} + +BOOL EscapeCommFunction(HANDLE hFile, DWORD dwFunc) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL TransmitCommChar(HANDLE hFile, char cChar) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL WaitCommEvent(HANDLE hFile, PDWORD lpEvtMask, LPOVERLAPPED lpOverlapped) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hFile; + + if (!CommInitialized()) + return FALSE; + + /* TODO: not implemented */ + + if (!pComm) + return FALSE; + + CommLog_Print(WLOG_ERROR, "Not implemented"); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +/** + * Returns TRUE on success, FALSE otherwise. To get extended error + * information, call GetLastError. + * + * ERRORS: + * ERROR_DLL_INIT_FAILED + * ERROR_OUTOFMEMORY was not possible to get mappings. + * ERROR_INVALID_DATA was not possible to add the device. + */ +BOOL DefineCommDevice(/* DWORD dwFlags,*/ LPCTSTR lpDeviceName, LPCTSTR lpTargetPath) +{ + LPTSTR storedDeviceName = NULL; + LPTSTR storedTargetPath = NULL; + + if (!CommInitialized()) + return FALSE; + + EnterCriticalSection(&_CommDevicesLock); + + if (_CommDevices == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + goto error_handle; + } + + storedDeviceName = _tcsdup(lpDeviceName); + + if (storedDeviceName == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + goto error_handle; + } + + storedTargetPath = _tcsdup(lpTargetPath); + + if (storedTargetPath == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + goto error_handle; + } + + int i = 0; + for (; i < COMM_DEVICE_MAX; i++) + { + if (_CommDevices[i] != NULL) + { + if (_tcscmp(_CommDevices[i]->name, storedDeviceName) == 0) + { + /* take over the emplacement */ + free(_CommDevices[i]->name); + free(_CommDevices[i]->path); + _CommDevices[i]->name = storedDeviceName; + _CommDevices[i]->path = storedTargetPath; + break; + } + } + else + { + /* new emplacement */ + _CommDevices[i] = (COMM_DEVICE*)calloc(1, sizeof(COMM_DEVICE)); + + if (_CommDevices[i] == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + goto error_handle; + } + + _CommDevices[i]->name = storedDeviceName; + _CommDevices[i]->path = storedTargetPath; + break; + } + } + + if (i == COMM_DEVICE_MAX) + { + SetLastError(ERROR_OUTOFMEMORY); + goto error_handle; + } + + LeaveCriticalSection(&_CommDevicesLock); + return TRUE; +error_handle: + free(storedDeviceName); + free(storedTargetPath); + LeaveCriticalSection(&_CommDevicesLock); + return FALSE; +} + +/** + * Returns the number of target paths in the buffer pointed to by + * lpTargetPath. + * + * The current implementation returns in any case 0 and 1 target + * path. A NULL lpDeviceName is not supported yet to get all the + * paths. + * + * ERRORS: + * ERROR_SUCCESS + * ERROR_DLL_INIT_FAILED + * ERROR_OUTOFMEMORY was not possible to get mappings. + * ERROR_NOT_SUPPORTED equivalent QueryDosDevice feature not supported. + * ERROR_INVALID_DATA was not possible to retrieve any device information. + * ERROR_INSUFFICIENT_BUFFER too small lpTargetPath + */ +DWORD QueryCommDevice(LPCTSTR lpDeviceName, LPTSTR lpTargetPath, DWORD ucchMax) +{ + LPTSTR storedTargetPath = NULL; + SetLastError(ERROR_SUCCESS); + + if (!CommInitialized()) + return 0; + + if (_CommDevices == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return 0; + } + + if (lpDeviceName == NULL || lpTargetPath == NULL) + { + SetLastError(ERROR_NOT_SUPPORTED); + return 0; + } + + EnterCriticalSection(&_CommDevicesLock); + storedTargetPath = NULL; + + for (int i = 0; i < COMM_DEVICE_MAX; i++) + { + if (_CommDevices[i] != NULL) + { + if (_tcscmp(_CommDevices[i]->name, lpDeviceName) == 0) + { + storedTargetPath = _CommDevices[i]->path; + break; + } + + continue; + } + + break; + } + + LeaveCriticalSection(&_CommDevicesLock); + + if (storedTargetPath == NULL) + { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + + if (_tcslen(storedTargetPath) + 2 > ucchMax) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return 0; + } + + _tcscpy(lpTargetPath, storedTargetPath); + lpTargetPath[_tcslen(storedTargetPath) + 1] = '\0'; /* 2nd final '\0' */ + return _tcslen(lpTargetPath) + 2; +} + +/** + * Checks whether lpDeviceName is a valid and registered Communication device. + */ +BOOL IsCommDevice(LPCTSTR lpDeviceName) +{ + TCHAR lpTargetPath[MAX_PATH]; + + if (!CommInitialized()) + return FALSE; + + if (QueryCommDevice(lpDeviceName, lpTargetPath, MAX_PATH) > 0) + { + return TRUE; + } + + return FALSE; +} + +/** + * Sets + */ +void _comm_setServerSerialDriver(HANDLE hComm, SERIAL_DRIVER_ID driverId) +{ + ULONG Type = 0; + WINPR_HANDLE* Object = NULL; + WINPR_COMM* pComm = NULL; + + if (!CommInitialized()) + return; + + if (!winpr_Handle_GetInfo(hComm, &Type, &Object)) + { + CommLog_Print(WLOG_WARN, "_comm_setServerSerialDriver failure"); + return; + } + + pComm = (WINPR_COMM*)Object; + pComm->serverSerialDriverId = driverId; +} + +static HANDLE_OPS ops = { CommIsHandled, CommCloseHandle, + CommGetFd, NULL, /* CleanupHandle */ + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL }; + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363198%28v=vs.85%29.aspx + * + * @param lpDeviceName e.g. COM1, ... + * + * @param dwDesiredAccess expects GENERIC_READ | GENERIC_WRITE, a + * warning message is printed otherwise. TODO: better support. + * + * @param dwShareMode must be zero, INVALID_HANDLE_VALUE is returned + * otherwise and GetLastError() should return ERROR_SHARING_VIOLATION. + * + * @param lpSecurityAttributes NULL expected, a warning message is printed + * otherwise. TODO: better support. + * + * @param dwCreationDisposition must be OPEN_EXISTING. If the + * communication device doesn't exist INVALID_HANDLE_VALUE is returned + * and GetLastError() returns ERROR_FILE_NOT_FOUND. + * + * @param dwFlagsAndAttributes zero expected, a warning message is + * printed otherwise. + * + * @param hTemplateFile must be NULL. + * + * @return INVALID_HANDLE_VALUE on error. + */ +HANDLE CommCreateFileA(LPCSTR lpDeviceName, DWORD dwDesiredAccess, DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) +{ + CHAR devicePath[MAX_PATH]; + struct stat deviceStat; + WINPR_COMM* pComm = NULL; + struct termios upcomingTermios; + + if (!CommInitialized()) + return INVALID_HANDLE_VALUE; + + if (dwDesiredAccess != (GENERIC_READ | GENERIC_WRITE)) + { + CommLog_Print(WLOG_WARN, "unexpected access to the device: 0x%08" PRIX32 "", + dwDesiredAccess); + } + + if (dwShareMode != 0) + { + SetLastError(ERROR_SHARING_VIOLATION); + return INVALID_HANDLE_VALUE; + } + + /* TODO: Prevents other processes from opening a file or + * device if they request delete, read, or write access. */ + + if (lpSecurityAttributes != NULL) + { + CommLog_Print(WLOG_WARN, "unexpected security attributes, nLength=%" PRIu32 "", + lpSecurityAttributes->nLength); + } + + if (dwCreationDisposition != OPEN_EXISTING) + { + SetLastError(ERROR_FILE_NOT_FOUND); /* FIXME: ERROR_NOT_SUPPORTED better? */ + return INVALID_HANDLE_VALUE; + } + + if (QueryCommDevice(lpDeviceName, devicePath, MAX_PATH) <= 0) + { + /* SetLastError(GetLastError()); */ + return INVALID_HANDLE_VALUE; + } + + if (stat(devicePath, &deviceStat) < 0) + { + CommLog_Print(WLOG_WARN, "device not found %s", devicePath); + SetLastError(ERROR_FILE_NOT_FOUND); + return INVALID_HANDLE_VALUE; + } + + if (!S_ISCHR(deviceStat.st_mode)) + { + CommLog_Print(WLOG_WARN, "bad device %s", devicePath); + SetLastError(ERROR_BAD_DEVICE); + return INVALID_HANDLE_VALUE; + } + + if (dwFlagsAndAttributes != 0) + { + CommLog_Print(WLOG_WARN, "unexpected flags and attributes: 0x%08" PRIX32 "", + dwFlagsAndAttributes); + } + + if (hTemplateFile != NULL) + { + SetLastError(ERROR_NOT_SUPPORTED); /* FIXME: other proper error? */ + return INVALID_HANDLE_VALUE; + } + + pComm = (WINPR_COMM*)calloc(1, sizeof(WINPR_COMM)); + + if (pComm == NULL) + { + SetLastError(ERROR_OUTOFMEMORY); + return INVALID_HANDLE_VALUE; + } + + WINPR_HANDLE_SET_TYPE_AND_MODE(pComm, HANDLE_TYPE_COMM, WINPR_FD_READ); + pComm->common.ops = &ops; + /* error_handle */ + pComm->fd = open(devicePath, O_RDWR | O_NOCTTY | O_NONBLOCK); + + if (pComm->fd < 0) + { + CommLog_Print(WLOG_WARN, "failed to open device %s", devicePath); + SetLastError(ERROR_BAD_DEVICE); + goto error_handle; + } + + pComm->fd_read = open(devicePath, O_RDONLY | O_NOCTTY | O_NONBLOCK); + + if (pComm->fd_read < 0) + { + CommLog_Print(WLOG_WARN, "failed to open fd_read, device: %s", devicePath); + SetLastError(ERROR_BAD_DEVICE); + goto error_handle; + } + + pComm->fd_read_event = eventfd( + 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */ + + if (pComm->fd_read_event < 0) + { + CommLog_Print(WLOG_WARN, "failed to open fd_read_event, device: %s", devicePath); + SetLastError(ERROR_BAD_DEVICE); + goto error_handle; + } + + InitializeCriticalSection(&pComm->ReadLock); + pComm->fd_write = open(devicePath, O_WRONLY | O_NOCTTY | O_NONBLOCK); + + if (pComm->fd_write < 0) + { + CommLog_Print(WLOG_WARN, "failed to open fd_write, device: %s", devicePath); + SetLastError(ERROR_BAD_DEVICE); + goto error_handle; + } + + pComm->fd_write_event = eventfd( + 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */ + + if (pComm->fd_write_event < 0) + { + CommLog_Print(WLOG_WARN, "failed to open fd_write_event, device: %s", devicePath); + SetLastError(ERROR_BAD_DEVICE); + goto error_handle; + } + + InitializeCriticalSection(&pComm->WriteLock); + /* can also be setup later on with _comm_setServerSerialDriver() */ + pComm->serverSerialDriverId = SerialDriverUnknown; + InitializeCriticalSection(&pComm->EventsLock); + + if (ioctl(pComm->fd, TIOCGICOUNT, &(pComm->counters)) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + CommLog_Print(WLOG_WARN, "could not read counters."); + /* could not initialize counters but keep on. + * + * Not all drivers, especially for USB to serial + * adapters (e.g. those based on pl2303), does support + * this call. + */ + ZeroMemory(&(pComm->counters), sizeof(struct serial_icounter_struct)); + } + + /* The binary/raw mode is required for the redirection but + * only flags that are not handle somewhere-else, except + * ICANON, are forced here. */ + ZeroMemory(&upcomingTermios, sizeof(struct termios)); + + if (tcgetattr(pComm->fd, &upcomingTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + goto error_handle; + } + + upcomingTermios.c_iflag &= + ~(/*IGNBRK |*/ BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL /*| IXON*/); + upcomingTermios.c_oflag = 0; /* <=> &= ~OPOST */ + upcomingTermios.c_lflag = 0; /* <=> &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); */ + /* upcomingTermios.c_cflag &= ~(CSIZE | PARENB); */ + /* upcomingTermios.c_cflag |= CS8; */ + /* About missing flags recommended by termios(3): + * + * IGNBRK and IXON, see: IOCTL_SERIAL_SET_HANDFLOW + * CSIZE, PARENB and CS8, see: IOCTL_SERIAL_SET_LINE_CONTROL + */ + /* a few more settings required for the redirection */ + upcomingTermios.c_cflag |= CLOCAL | CREAD; + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + goto error_handle; + } + + return (HANDLE)pComm; +error_handle: + CloseHandle(pComm); + return INVALID_HANDLE_VALUE; +} + +BOOL CommIsHandled(HANDLE handle) +{ + if (!CommInitialized()) + return FALSE; + + return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_COMM, TRUE); +} + +BOOL CommIsHandleValid(HANDLE handle) +{ + WINPR_COMM* pComm = (WINPR_COMM*)handle; + if (!CommIsHandled(handle)) + return FALSE; + if (pComm->fd <= 0) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + return TRUE; +} + +BOOL CommCloseHandle(HANDLE handle) +{ + WINPR_COMM* pComm = (WINPR_COMM*)handle; + + if (!CommIsHandled(handle)) + return FALSE; + + if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING) + { + ULONG WaitMask = 0; + DWORD BytesReturned = 0; + + /* ensures to gracefully stop the WAIT_ON_MASK's loop */ + if (!CommDeviceIoControl(handle, IOCTL_SERIAL_SET_WAIT_MASK, &WaitMask, sizeof(ULONG), NULL, + 0, &BytesReturned, NULL)) + { + CommLog_Print(WLOG_WARN, "failure to WAIT_ON_MASK's loop!"); + } + } + + DeleteCriticalSection(&pComm->ReadLock); + DeleteCriticalSection(&pComm->WriteLock); + DeleteCriticalSection(&pComm->EventsLock); + + if (pComm->fd > 0) + close(pComm->fd); + + if (pComm->fd_write > 0) + close(pComm->fd_write); + + if (pComm->fd_write_event > 0) + close(pComm->fd_write_event); + + if (pComm->fd_read > 0) + close(pComm->fd_read); + + if (pComm->fd_read_event > 0) + close(pComm->fd_read_event); + + free(pComm); + return TRUE; +} + +#ifndef WITH_EVENTFD_READ_WRITE +int eventfd_read(int fd, eventfd_t* value) +{ + return (read(fd, value, sizeof(*value)) == sizeof(*value)) ? 0 : -1; +} + +int eventfd_write(int fd, eventfd_t value) +{ + return (write(fd, &value, sizeof(value)) == sizeof(value)) ? 0 : -1; +} +#endif + +#endif /* __linux__ */ |