/** * WinPR: Windows Portable Runtime * Serial Communication API * * Copyright 2011 O.S. Systems Software Ltda. * Copyright 2011 Eduardo Fiss Beloni * Copyright 2014 Marc-Andre Moreau * 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 #include #include #include #include #include #if defined(WINPR_HAVE_SYS_EVENTFD_H) #include #endif #include #include #include #include #include #include #include #include #include #include #include "comm_ioctl.h" #include "../log.h" #define TAG WINPR_TAG("comm") /** * Communication Resources: * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363196/ */ #include "comm.h" static wLog* sLog = nullptr; 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 nullptr-terminated array with a maximum of COMM_DEVICE_MAX COMM_DEVICE */ #define COMM_DEVICE_MAX 128 static COMM_DEVICE** sCommDevices = nullptr; static CRITICAL_SECTION sCommDevicesLock = WINPR_C_ARRAY_INIT; static pthread_once_t sCommInitialized = PTHREAD_ONCE_INIT; static const _SERIAL_IOCTL_NAME S_SERIAL_IOCTL_NAMES[] = { { IOCTL_SERIAL_SET_BAUD_RATE, "IOCTL_SERIAL_SET_BAUD_RATE" }, { IOCTL_SERIAL_GET_BAUD_RATE, "IOCTL_SERIAL_GET_BAUD_RATE" }, { IOCTL_SERIAL_SET_LINE_CONTROL, "IOCTL_SERIAL_SET_LINE_CONTROL" }, { IOCTL_SERIAL_GET_LINE_CONTROL, "IOCTL_SERIAL_GET_LINE_CONTROL" }, { IOCTL_SERIAL_SET_TIMEOUTS, "IOCTL_SERIAL_SET_TIMEOUTS" }, { IOCTL_SERIAL_GET_TIMEOUTS, "IOCTL_SERIAL_GET_TIMEOUTS" }, { IOCTL_SERIAL_GET_CHARS, "IOCTL_SERIAL_GET_CHARS" }, { IOCTL_SERIAL_SET_CHARS, "IOCTL_SERIAL_SET_CHARS" }, { IOCTL_SERIAL_SET_DTR, "IOCTL_SERIAL_SET_DTR" }, { IOCTL_SERIAL_CLR_DTR, "IOCTL_SERIAL_CLR_DTR" }, { IOCTL_SERIAL_RESET_DEVICE, "IOCTL_SERIAL_RESET_DEVICE" }, { IOCTL_SERIAL_SET_RTS, "IOCTL_SERIAL_SET_RTS" }, { IOCTL_SERIAL_CLR_RTS, "IOCTL_SERIAL_CLR_RTS" }, { IOCTL_SERIAL_SET_XOFF, "IOCTL_SERIAL_SET_XOFF" }, { IOCTL_SERIAL_SET_XON, "IOCTL_SERIAL_SET_XON" }, { IOCTL_SERIAL_SET_BREAK_ON, "IOCTL_SERIAL_SET_BREAK_ON" }, { IOCTL_SERIAL_SET_BREAK_OFF, "IOCTL_SERIAL_SET_BREAK_OFF" }, { IOCTL_SERIAL_SET_QUEUE_SIZE, "IOCTL_SERIAL_SET_QUEUE_SIZE" }, { IOCTL_SERIAL_GET_WAIT_MASK, "IOCTL_SERIAL_GET_WAIT_MASK" }, { IOCTL_SERIAL_SET_WAIT_MASK, "IOCTL_SERIAL_SET_WAIT_MASK" }, { IOCTL_SERIAL_WAIT_ON_MASK, "IOCTL_SERIAL_WAIT_ON_MASK" }, { IOCTL_SERIAL_IMMEDIATE_CHAR, "IOCTL_SERIAL_IMMEDIATE_CHAR" }, { IOCTL_SERIAL_PURGE, "IOCTL_SERIAL_PURGE" }, { IOCTL_SERIAL_GET_HANDFLOW, "IOCTL_SERIAL_GET_HANDFLOW" }, { IOCTL_SERIAL_SET_HANDFLOW, "IOCTL_SERIAL_SET_HANDFLOW" }, { IOCTL_SERIAL_GET_MODEMSTATUS, "IOCTL_SERIAL_GET_MODEMSTATUS" }, { IOCTL_SERIAL_GET_DTRRTS, "IOCTL_SERIAL_GET_DTRRTS" }, { IOCTL_SERIAL_GET_COMMSTATUS, "IOCTL_SERIAL_GET_COMMSTATUS" }, { IOCTL_SERIAL_GET_PROPERTIES, "IOCTL_SERIAL_GET_PROPERTIES" }, // {IOCTL_SERIAL_XOFF_COUNTER, "IOCTL_SERIAL_XOFF_COUNTER"}, // {IOCTL_SERIAL_LSRMST_INSERT, "IOCTL_SERIAL_LSRMST_INSERT"}, { IOCTL_SERIAL_CONFIG_SIZE, "IOCTL_SERIAL_CONFIG_SIZE" }, // {IOCTL_SERIAL_GET_STATS, "IOCTL_SERIAL_GET_STATS"}, // {IOCTL_SERIAL_CLEAR_STATS, "IOCTL_SERIAL_CLEAR_STATS"}, // {IOCTL_SERIAL_GET_MODEM_CONTROL,"IOCTL_SERIAL_GET_MODEM_CONTROL"}, // {IOCTL_SERIAL_SET_MODEM_CONTROL,"IOCTL_SERIAL_SET_MODEM_CONTROL"}, // {IOCTL_SERIAL_SET_FIFO_CONTROL, "IOCTL_SERIAL_SET_FIFO_CONTROL"}, // {IOCTL_PAR_QUERY_INFORMATION, "IOCTL_PAR_QUERY_INFORMATION"}, // {IOCTL_PAR_SET_INFORMATION, "IOCTL_PAR_SET_INFORMATION"}, // {IOCTL_PAR_QUERY_DEVICE_ID, "IOCTL_PAR_QUERY_DEVICE_ID"}, // {IOCTL_PAR_QUERY_DEVICE_ID_SIZE,"IOCTL_PAR_QUERY_DEVICE_ID_SIZE"}, // {IOCTL_IEEE1284_GET_MODE, "IOCTL_IEEE1284_GET_MODE"}, // {IOCTL_IEEE1284_NEGOTIATE, "IOCTL_IEEE1284_NEGOTIATE"}, // {IOCTL_PAR_SET_WRITE_ADDRESS, "IOCTL_PAR_SET_WRITE_ADDRESS"}, // {IOCTL_PAR_SET_READ_ADDRESS, "IOCTL_PAR_SET_READ_ADDRESS"}, // {IOCTL_PAR_GET_DEVICE_CAPS, "IOCTL_PAR_GET_DEVICE_CAPS"}, // {IOCTL_PAR_GET_DEFAULT_MODES, "IOCTL_PAR_GET_DEFAULT_MODES"}, // {IOCTL_PAR_QUERY_RAW_DEVICE_ID, "IOCTL_PAR_QUERY_RAW_DEVICE_ID"}, // {IOCTL_PAR_IS_PORT_FREE, "IOCTL_PAR_IS_PORT_FREE"}, { IOCTL_USBPRINT_GET_1284_ID, "IOCTL_USBPRINT_GET_1284_ID" } }; const char* _comm_serial_ioctl_name(ULONG number) { for (size_t x = 0; x < ARRAYSIZE(S_SERIAL_IOCTL_NAMES); x++) { const _SERIAL_IOCTL_NAME* cur = &S_SERIAL_IOCTL_NAMES[x]; if (cur->number == number) return cur->name; } return "(unknown ioctl name)"; } static int CommGetFd(HANDLE handle) { WINPR_COMM* comm = (WINPR_COMM*)handle; if (!CommIsHandled(handle)) return -1; return comm->fd; } const HANDLE_CREATOR* GetCommHandleCreator(void) { #if defined(WINPR_HAVE_SERIAL_SUPPORT) static const HANDLE_CREATOR sCommHandleCreator = { .IsHandled = IsCommDevice, .CreateFileA = CommCreateFileA }; return &sCommHandleCreator; #else return nullptr; #endif } static void CommInit(void) { /* NB: error management to be done outside of this function */ WINPR_ASSERT(sLog == nullptr); WINPR_ASSERT(sCommDevices == nullptr); sCommDevices = (COMM_DEVICE**)calloc(COMM_DEVICE_MAX + 1, sizeof(COMM_DEVICE*)); if (!sCommDevices) return; if (!InitializeCriticalSectionEx(&sCommDevicesLock, 0, 0)) { free((void*)sCommDevices); sCommDevices = nullptr; return; } sLog = WLog_Get(TAG); WINPR_ASSERT(sLog != nullptr); } /** * Returns TRUE when the comm module is correctly initialized, FALSE otherwise * with ERROR_DLL_INIT_FAILED set as the last error. */ static BOOL CommInitialized(void) { if (pthread_once(&sCommInitialized, CommInit) != 0) { SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } return TRUE; } WINPR_ATTR_FORMAT_ARG(5, 6) void CommLog_PrintEx(DWORD level, const char* file, size_t line, const char* fkt, WINPR_FORMAT_ARG const char* fmt, ...) { if (!CommInitialized()) return; if (!WLog_IsLevelActive(sLog, level)) return; va_list ap = WINPR_C_ARRAY_INIT; va_start(ap, fmt); WLog_PrintTextMessageVA(sLog, level, line, file, fkt, fmt, ap); va_end(ap); } BOOL BuildCommDCBA(WINPR_ATTR_UNUSED LPCSTR lpDef, WINPR_ATTR_UNUSED LPDCB lpDCB) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL BuildCommDCBW(WINPR_ATTR_UNUSED LPCWSTR lpDef, WINPR_ATTR_UNUSED LPDCB lpDCB) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL BuildCommDCBAndTimeoutsA(WINPR_ATTR_UNUSED LPCSTR lpDef, WINPR_ATTR_UNUSED LPDCB lpDCB, WINPR_ATTR_UNUSED LPCOMMTIMEOUTS lpCommTimeouts) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL BuildCommDCBAndTimeoutsW(WINPR_ATTR_UNUSED LPCWSTR lpDef, WINPR_ATTR_UNUSED LPDCB lpDCB, WINPR_ATTR_UNUSED LPCOMMTIMEOUTS lpCommTimeouts) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL CommConfigDialogA(WINPR_ATTR_UNUSED LPCSTR lpszName, WINPR_ATTR_UNUSED HWND hWnd, WINPR_ATTR_UNUSED LPCOMMCONFIG lpCC) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL CommConfigDialogW(WINPR_ATTR_UNUSED LPCWSTR lpszName, WINPR_ATTR_UNUSED HWND hWnd, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED LPCOMMCONFIG lpCC, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED LPCOMMCONFIG lpCC, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED 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, nullptr, 0, lpCommProp, sizeof(COMMPROP), &bytesReturned, nullptr)) { 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 = nullptr; 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 == nullptr) { SetLastError(ERROR_OUTOFMEMORY); return FALSE; } /* error_handle */ lpLocalDcb->DCBlength = lpDCB->DCBlength; SERIAL_BAUD_RATE baudRate; if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_BAUD_RATE, nullptr, 0, &baudRate, sizeof(SERIAL_BAUD_RATE), &bytesReturned, nullptr)) { 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, nullptr, 0, &handflow, sizeof(SERIAL_HANDFLOW), &bytesReturned, nullptr)) { 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 = WINPR_ASSERTING_INT_CAST(WORD, handflow.XonLimit); lpLocalDcb->XoffLim = WINPR_ASSERTING_INT_CAST(WORD, handflow.XoffLimit); { SERIAL_LINE_CONTROL lineControl = WINPR_C_ARRAY_INIT; if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_LINE_CONTROL, nullptr, 0, &lineControl, sizeof(SERIAL_LINE_CONTROL), &bytesReturned, nullptr)) { 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 = WINPR_C_ARRAY_INIT; if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, nullptr, 0, &serialChars, sizeof(SERIAL_CHARS), &bytesReturned, nullptr)) { 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 = WINPR_C_ARRAY_INIT; 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), nullptr, 0, &bytesReturned, nullptr)) { CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the baud rate."); return FALSE; } SERIAL_CHARS serialChars; if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, nullptr, 0, &serialChars, sizeof(SERIAL_CHARS), &bytesReturned, nullptr)) /* 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), nullptr, 0, &bytesReturned, nullptr)) { 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), nullptr, 0, &bytesReturned, nullptr)) { CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the control settings."); return FALSE; } SERIAL_HANDFLOW handflow = WINPR_C_ARRAY_INIT; 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: %" PRId32 "\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: %" PRId32 "\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), nullptr, 0, &bytesReturned, nullptr)) { 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 &= (tcflag_t)~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 &= (tcflag_t)~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, nullptr, 0, lpCommTimeouts, sizeof(COMMTIMEOUTS), &bytesReturned, nullptr)) { 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), nullptr, 0, &bytesReturned, nullptr)) { CommLog_Print(WLOG_WARN, "SetCommTimeouts failure."); return FALSE; } return TRUE; } BOOL GetDefaultCommConfigA(WINPR_ATTR_UNUSED LPCSTR lpszName, WINPR_ATTR_UNUSED LPCOMMCONFIG lpCC, WINPR_ATTR_UNUSED LPDWORD lpdwSize) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL GetDefaultCommConfigW(WINPR_ATTR_UNUSED LPCWSTR lpszName, WINPR_ATTR_UNUSED LPCOMMCONFIG lpCC, WINPR_ATTR_UNUSED LPDWORD lpdwSize) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL SetDefaultCommConfigA(WINPR_ATTR_UNUSED LPCSTR lpszName, WINPR_ATTR_UNUSED LPCOMMCONFIG lpCC, WINPR_ATTR_UNUSED DWORD dwSize) { if (!CommInitialized()) return FALSE; /* TODO: not implemented */ CommLog_Print(WLOG_ERROR, "Not implemented"); SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } BOOL SetDefaultCommConfigW(WINPR_ATTR_UNUSED LPCWSTR lpszName, WINPR_ATTR_UNUSED LPCOMMCONFIG lpCC, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED PDWORD lpErrors, WINPR_ATTR_UNUSED 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), nullptr, 0, &bytesReturned, nullptr)) { 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), nullptr, 0, &bytesReturned, nullptr)) { CommLog_Print(WLOG_WARN, "SetCommTimeouts failure."); return FALSE; } return TRUE; } BOOL EscapeCommFunction(HANDLE hFile, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED 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, WINPR_ATTR_UNUSED PDWORD lpEvtMask, WINPR_ATTR_UNUSED 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 = nullptr; LPTSTR storedTargetPath = nullptr; if (!CommInitialized()) return FALSE; EnterCriticalSection(&sCommDevicesLock); if (sCommDevices == nullptr) { SetLastError(ERROR_DLL_INIT_FAILED); goto error_handle; } storedDeviceName = _tcsdup(lpDeviceName); if (storedDeviceName == nullptr) { SetLastError(ERROR_OUTOFMEMORY); goto error_handle; } storedTargetPath = _tcsdup(lpTargetPath); if (storedTargetPath == nullptr) { SetLastError(ERROR_OUTOFMEMORY); goto error_handle; } { int i = 0; for (; i < COMM_DEVICE_MAX; i++) { if (sCommDevices[i] != nullptr) { if (_tcscmp(sCommDevices[i]->name, storedDeviceName) == 0) { /* take over the emplacement */ free(sCommDevices[i]->name); free(sCommDevices[i]->path); sCommDevices[i]->name = storedDeviceName; sCommDevices[i]->path = storedTargetPath; break; } } else { /* new emplacement */ sCommDevices[i] = (COMM_DEVICE*)calloc(1, sizeof(COMM_DEVICE)); if (sCommDevices[i] == nullptr) { SetLastError(ERROR_OUTOFMEMORY); goto error_handle; } sCommDevices[i]->name = storedDeviceName; sCommDevices[i]->path = storedTargetPath; break; } } if (i == COMM_DEVICE_MAX) { SetLastError(ERROR_OUTOFMEMORY); goto error_handle; } } LeaveCriticalSection(&sCommDevicesLock); return TRUE; error_handle: free(storedDeviceName); free(storedTargetPath); LeaveCriticalSection(&sCommDevicesLock); 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 nullptr 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 = nullptr; SetLastError(ERROR_SUCCESS); if (!CommInitialized()) return 0; if (sCommDevices == nullptr) { SetLastError(ERROR_DLL_INIT_FAILED); return 0; } if (lpDeviceName == nullptr || lpTargetPath == nullptr) { SetLastError(ERROR_NOT_SUPPORTED); return 0; } EnterCriticalSection(&sCommDevicesLock); storedTargetPath = nullptr; for (int i = 0; i < COMM_DEVICE_MAX; i++) { if (sCommDevices[i] != nullptr) { if (_tcscmp(sCommDevices[i]->name, lpDeviceName) == 0) { storedTargetPath = sCommDevices[i]->path; break; } continue; } break; } LeaveCriticalSection(&sCommDevicesLock); if (storedTargetPath == nullptr) { SetLastError(ERROR_INVALID_DATA); return 0; } const size_t size = _tcsnlen(storedTargetPath, ucchMax); if (size + 2 > ucchMax) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return 0; } _tcsncpy(lpTargetPath, storedTargetPath, size + 1); lpTargetPath[size + 2] = '\0'; /* 2nd final '\0' */ return (DWORD)size + 2UL; } /** * 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 = nullptr; WINPR_COMM* pComm = nullptr; 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, nullptr, /* CleanupHandle */ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; /** * 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 nullptr 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 nullptr. * * @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] = WINPR_C_ARRAY_INIT; struct stat deviceStat = WINPR_C_ARRAY_INIT; WINPR_COMM* pComm = nullptr; struct termios upcomingTermios = WINPR_C_ARRAY_INIT; 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 != nullptr) { 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 != nullptr) { SetLastError(ERROR_NOT_SUPPORTED); /* FIXME: other proper error? */ return INVALID_HANDLE_VALUE; } pComm = (WINPR_COMM*)calloc(1, sizeof(WINPR_COMM)); if (pComm == nullptr) { 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; } #if defined(WINPR_HAVE_SYS_EVENTFD_H) pComm->fd_read_event = eventfd( 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */ #endif 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; } #if defined(WINPR_HAVE_SYS_EVENTFD_H) pComm->fd_write_event = eventfd( 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */ #endif 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); (void)CommUpdateIOCount(pComm, TRUE); /* 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 &= (tcflag_t) ~(/*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: WINPR_PRAGMA_DIAG_PUSH WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC(void) CloseHandle(pComm); WINPR_PRAGMA_DIAG_POP 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; 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; } #if defined(WINPR_HAVE_SYS_EVENTFD_H) #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 static const char* CommIoCtlToStr(unsigned long int io) { switch (io) { #if defined(WINPR_HAVE_SERIAL_SUPPORT) #if defined(TCGETS) case TCGETS: return "TCGETS"; #endif #if defined(TCSETS) case TCSETS: return "TCSETS"; #endif #if defined(TCSETSW) case TCSETSW: return "TCSETSW"; #endif #if defined(TCSETSF) case TCSETSF: return "TCSETSF"; #endif #if defined(TCGETA) case TCGETA: return "TCGETA"; #endif #if defined(TCSETA) case TCSETA: return "TCSETA"; #endif #if defined(TCSETAW) case TCSETAW: return "TCSETAW"; #endif #if defined(TCSETAF) case TCSETAF: return "TCSETAF"; #endif #if defined(TCSBRK) case TCSBRK: return "TCSBRK"; #endif #if defined(TCXONC) case TCXONC: return "TCXONC"; #endif #if defined(TCFLSH) case TCFLSH: return "TCFLSH"; #endif #if defined(TIOCEXCL) case TIOCEXCL: return "TIOCEXCL"; #endif #if defined(TIOCNXCL) case TIOCNXCL: return "TIOCNXCL"; #endif #if defined(TIOCSCTTY) case TIOCSCTTY: return "TIOCSCTTY"; #endif #if defined(TIOCGPGRP) case TIOCGPGRP: return "TIOCGPGRP"; #endif #if defined(TIOCSPGRP) case TIOCSPGRP: return "TIOCSPGRP"; #endif #if defined(TIOCOUTQ) case TIOCOUTQ: return "TIOCOUTQ"; #endif #if defined(TIOCSTI) case TIOCSTI: return "TIOCSTI"; #endif #if defined(TIOCGWINSZ) case TIOCGWINSZ: return "TIOCGWINSZ"; #endif #if defined(TIOCSWINSZ) case TIOCSWINSZ: return "TIOCSWINSZ"; #endif #if defined(TIOCMGET) case TIOCMGET: return "TIOCMGET"; #endif #if defined(TIOCMBIS) case TIOCMBIS: return "TIOCMBIS"; #endif #if defined(TIOCMBIC) case TIOCMBIC: return "TIOCMBIC"; #endif #if defined(TIOCMSET) case TIOCMSET: return "TIOCMSET"; #endif #if defined(TIOCGSOFTCAR) case TIOCGSOFTCAR: return "TIOCGSOFTCAR"; #endif #if defined(TIOCSSOFTCAR) case TIOCSSOFTCAR: return "TIOCSSOFTCAR"; #endif #if defined(FIONREAD) case FIONREAD: return "FIONREAD/TIOCINQ"; #endif #if defined(TIOCLINUX) case TIOCLINUX: return "TIOCLINUX"; #endif #if defined(TIOCCONS) case TIOCCONS: return "TIOCCONS"; #endif #if defined(TIOCGSERIAL) case TIOCGSERIAL: return "TIOCGSERIAL"; #endif #if defined(TIOCSSERIAL) case TIOCSSERIAL: return "TIOCSSERIAL"; #endif #if defined(TIOCPKT) case TIOCPKT: return "TIOCPKT"; #endif #if defined(FIONBIO) case FIONBIO: return "FIONBIO"; #endif #if defined(TIOCNOTTY) case TIOCNOTTY: return "TIOCNOTTY"; #endif #if defined(TIOCSETD) case TIOCSETD: return "TIOCSETD"; #endif #if defined(TIOCGETD) case TIOCGETD: return "TIOCGETD"; #endif #if defined(TCSBRKP) case TCSBRKP: return "TCSBRKP"; #endif #if defined(TIOCSBRK) case TIOCSBRK: return "TIOCSBRK"; #endif #if defined(TIOCCBRK) case TIOCCBRK: return "TIOCCBRK"; #endif #if defined(TIOCGSID) case TIOCGSID: return "TIOCGSID"; #endif #if defined(TIOCGRS485) case TIOCGRS485: return "TIOCGRS485"; #endif #if defined(TIOCSRS485) case TIOCSRS485: return "TIOCSRS485"; #endif #if defined(TIOCSPTLCK) case TIOCSPTLCK: return "TIOCSPTLCK"; #endif #if defined(TCGETX) case TCGETX: return "TCGETX"; #endif #if defined(TCSETX) case TCSETX: return "TCSETX"; #endif #if defined(TCSETXF) case TCSETXF: return "TCSETXF"; #endif #if defined(TCSETXW) case TCSETXW: return "TCSETXW"; #endif #if defined(TIOCSIG) case TIOCSIG: return "TIOCSIG"; #endif #if defined(TIOCVHANGUP) case TIOCVHANGUP: return "TIOCVHANGUP"; #endif #if defined(TIOCGPTPEER) case TIOCGPTPEER: return "TIOCGPTPEER"; #endif #if defined(FIONCLEX) case FIONCLEX: return "FIONCLEX"; #endif #if defined(FIOCLEX) case FIOCLEX: return "FIOCLEX"; #endif #if defined(FIOASYNC) case FIOASYNC: return "FIOASYNC"; #endif #if defined(TIOCSERCONFIG) case TIOCSERCONFIG: return "TIOCSERCONFIG"; #endif #if defined(TIOCSERGWILD) case TIOCSERGWILD: return "TIOCSERGWILD"; #endif #if defined(TIOCSERSWILD) case TIOCSERSWILD: return "TIOCSERSWILD"; #endif #if defined(TIOCGLCKTRMIOS) case TIOCGLCKTRMIOS: return "TIOCGLCKTRMIOS"; #endif #if defined(TIOCSLCKTRMIOS) case TIOCSLCKTRMIOS: return "TIOCSLCKTRMIOS"; #endif #if defined(TIOCSERGSTRUCT) case TIOCSERGSTRUCT: return "TIOCSERGSTRUCT"; #endif #if defined(TIOCSERGETLSR) case TIOCSERGETLSR: return "TIOCSERGETLSR"; #endif #if defined(TIOCSERGETMULTI) case TIOCSERGETMULTI: return "TIOCSERGETMULTI"; #endif #if defined(TIOCSERSETMULTI) case TIOCSERSETMULTI: return "TIOCSERSETMULTI"; #endif #if defined(TIOCMIWAIT) case TIOCMIWAIT: return "TIOCMIWAIT"; #endif #if defined(TIOCGICOUNT) case TIOCGICOUNT: return "TIOCGICOUNT"; #endif #if defined(FIOQSIZE) case FIOQSIZE: return "FIOQSIZE"; #endif #if defined(TIOCPKT_DATA) case TIOCPKT_DATA: return "TIOCPKT_DATA"; #endif #if defined(TIOCPKT_FLUSHWRITE) case TIOCPKT_FLUSHWRITE: return "TIOCPKT_FLUSHWRITE"; #endif #if defined(TIOCPKT_STOP) case TIOCPKT_STOP: return "TIOCPKT_STOP"; #endif #if defined(TIOCPKT_START) case TIOCPKT_START: return "TIOCPKT_START"; #endif #if defined(TIOCPKT_NOSTOP) case TIOCPKT_NOSTOP: return "TIOCPKT_NOSTOP"; #endif #if defined(TIOCPKT_DOSTOP) case TIOCPKT_DOSTOP: return "TIOCPKT_DOSTOP"; #endif #if defined(TIOCPKT_IOCTL) case TIOCPKT_IOCTL: return "TIOCPKT_IOCTL"; #endif #endif default: return "UNKNOWN"; } } static BOOL CommStatusErrorEx(WINPR_COMM* pComm, unsigned long int ctl, const char* file, const char* fkt, size_t line) { WINPR_ASSERT(pComm); BOOL rc = (pComm->permissive); const DWORD level = rc ? WLOG_DEBUG : WLOG_WARN; char ebuffer[256] = WINPR_C_ARRAY_INIT; const char* str = CommIoCtlToStr(ctl); if (CommInitialized()) { if (WLog_IsLevelActive(sLog, level)) { WLog_PrintTextMessage(sLog, level, line, file, fkt, "%s [0x%08lx] ioctl failed, errno=[%d] %s.", str, ctl, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer))); } } if (!rc) SetLastError(ERROR_IO_DEVICE); return rc; } BOOL CommIoCtl_int(WINPR_COMM* pComm, unsigned long int ctl, void* data, const char* file, const char* fkt, size_t line) { if (ioctl(pComm->fd, ctl, data) < 0) { if (!CommStatusErrorEx(pComm, ctl, file, fkt, line)) return FALSE; } return TRUE; } BOOL CommUpdateIOCount(WINPR_ATTR_UNUSED HANDLE handle, WINPR_ATTR_UNUSED BOOL checkSupportStatus) { WINPR_COMM* pComm = (WINPR_COMM*)handle; WINPR_ASSERT(pComm); #if defined(WINPR_HAVE_COMM_COUNTERS) ZeroMemory(&(pComm->counters), sizeof(struct serial_icounter_struct)); if (pComm->TIOCGICOUNTSupported || checkSupportStatus) { const int rc = ioctl(pComm->fd, TIOCGICOUNT, &(pComm->counters)); if (checkSupportStatus) pComm->TIOCGICOUNTSupported = rc >= 0; else if (rc < 0) { if (!CommStatusErrorEx(pComm, TIOCGICOUNT, __FILE__, __func__, __LINE__)) return FALSE; } } #endif return TRUE; } static const char* CommSerialEvFlagString(ULONG flag) { switch (flag) { case SERIAL_EV_RXCHAR: return "SERIAL_EV_RXCHAR"; case SERIAL_EV_RXFLAG: return "SERIAL_EV_RXFLAG"; case SERIAL_EV_TXEMPTY: return "SERIAL_EV_TXEMPTY"; case SERIAL_EV_CTS: return "SERIAL_EV_CTS "; case SERIAL_EV_DSR: return "SERIAL_EV_DSR "; case SERIAL_EV_RLSD: return "SERIAL_EV_RLSD"; case SERIAL_EV_BREAK: return "SERIAL_EV_BREAK"; case SERIAL_EV_ERR: return "SERIAL_EV_ERR "; case SERIAL_EV_RING: return "SERIAL_EV_RING"; case SERIAL_EV_PERR: return "SERIAL_EV_PERR"; case SERIAL_EV_RX80FULL: return "SERIAL_EV_RX80FULL"; case SERIAL_EV_EVENT1: return "SERIAL_EV_EVENT1"; case SERIAL_EV_EVENT2: return "SERIAL_EV_EVENT2"; case SERIAL_EV_WINPR_WAITING: return "SERIAL_EV_WINPR_WAITING"; case SERIAL_EV_WINPR_STOP: return "SERIAL_EV_WINPR_STOP"; default: return "SERIAL_EV_UNKNOWN"; } } const char* CommSerialEvString(ULONG status, char* buffer, size_t size) { const ULONG flags[] = { SERIAL_EV_RXCHAR, SERIAL_EV_RXFLAG, SERIAL_EV_TXEMPTY, SERIAL_EV_CTS, SERIAL_EV_DSR, SERIAL_EV_RLSD, SERIAL_EV_BREAK, SERIAL_EV_ERR, SERIAL_EV_RING, SERIAL_EV_PERR, SERIAL_EV_RX80FULL, SERIAL_EV_EVENT1, SERIAL_EV_EVENT2, SERIAL_EV_WINPR_WAITING, SERIAL_EV_WINPR_STOP }; winpr_str_append("{", buffer, size, ""); const char* sep = ""; for (size_t x = 0; x < ARRAYSIZE(flags); x++) { const ULONG flag = flags[x]; if (status & flag) { winpr_str_append(CommSerialEvFlagString(flag), buffer, size, sep); sep = "|"; } } char number[32] = WINPR_C_ARRAY_INIT; (void)_snprintf(number, sizeof(number), "}[0x%08" PRIx32 "]", status); winpr_str_append(number, buffer, size, ""); return buffer; }