diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/comm/comm_io.c | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'winpr/libwinpr/comm/comm_io.c')
-rw-r--r-- | winpr/libwinpr/comm/comm_io.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/winpr/libwinpr/comm/comm_io.c b/winpr/libwinpr/comm/comm_io.c new file mode 100644 index 0000000..9904eab --- /dev/null +++ b/winpr/libwinpr/comm/comm_io.c @@ -0,0 +1,549 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * 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 <termios.h> +#include <unistd.h> + +#include <winpr/io.h> +#include <winpr/wlog.h> +#include <winpr/wtypes.h> + +#include "comm.h" + +BOOL _comm_set_permissive(HANDLE hDevice, BOOL permissive) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hDevice; + + if (!CommIsHandled(hDevice)) + return FALSE; + + pComm->permissive = permissive; + return TRUE; +} + +/* Computes VTIME in deciseconds from Ti in milliseconds */ +static UCHAR _vtime(ULONG Ti) +{ + /* FIXME: look for an equivalent math function otherwise let + * do the compiler do the optimization */ + if (Ti == 0) + return 0; + else if (Ti < 100) + return 1; + else if (Ti > 25500) + return 255; /* 0xFF */ + else + return Ti / 100; +} + +/** + * ERRORS: + * ERROR_INVALID_HANDLE + * ERROR_NOT_SUPPORTED + * ERROR_INVALID_PARAMETER + * ERROR_TIMEOUT + * ERROR_IO_DEVICE + * ERROR_BAD_DEVICE + */ +BOOL CommReadFile(HANDLE hDevice, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, + LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hDevice; + int biggestFd = -1; + fd_set read_set; + int nbFds = 0; + COMMTIMEOUTS* pTimeouts = NULL; + UCHAR vmin = 0; + UCHAR vtime = 0; + ULONGLONG Tmax = 0; + struct timeval tmaxTimeout; + struct timeval* pTmaxTimeout = NULL; + struct termios currentTermios; + EnterCriticalSection(&pComm->ReadLock); /* KISSer by the function's beginning */ + + if (!CommIsHandled(hDevice)) + goto return_false; + + if (lpOverlapped != NULL) + { + SetLastError(ERROR_NOT_SUPPORTED); + goto return_false; + } + + if (lpNumberOfBytesRead == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */ + goto return_false; + } + + *lpNumberOfBytesRead = 0; /* will be ajusted if required ... */ + + if (nNumberOfBytesToRead <= 0) /* N */ + { + goto return_true; /* FIXME: or FALSE? */ + } + + if (tcgetattr(pComm->fd, ¤tTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + + if (currentTermios.c_lflag & ICANON) + { + CommLog_Print(WLOG_WARN, "Canonical mode not supported"); /* the timeout could not be set */ + SetLastError(ERROR_NOT_SUPPORTED); + goto return_false; + } + + /* http://msdn.microsoft.com/en-us/library/hh439614%28v=vs.85%29.aspx + * http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx + * + * ReadIntervalTimeout | ReadTotalTimeoutMultiplier | ReadTotalTimeoutConstant | VMIN | VTIME | + * TMAX | 0 | 0 | 0 | N | 0 | + * INDEF | Blocks for N bytes available. 0< Ti <MAXULONG | 0 | 0 | + * N | Ti | INDEF | Blocks on first byte, then use Ti between bytes. MAXULONG | 0 | 0 + * | 0 | 0 | 0 | Returns immediately with bytes available (don't block) MAXULONG | + * MAXULONG | 0< Tc <MAXULONG | N | 0 | Tc | Blocks on first byte + * during Tc or returns immediately whith bytes available MAXULONG | m | + * MAXULONG | | Invalid 0 | m | 0< Tc + * <MAXULONG | N | 0 | Tmax | Blocks on first byte during Tmax or returns + * immediately whith bytes available 0< Ti <MAXULONG | m | 0< + * Tc <MAXULONG | N | Ti | Tmax | Blocks on first byte, then use Ti between bytes. + * Tmax is used for the whole system call. + */ + /* NB: timeouts are in milliseconds, VTIME are in deciseconds and is an unsigned char */ + /* FIXME: double check whether open(pComm->fd_read_event, O_NONBLOCK) doesn't conflict with + * above use cases */ + pTimeouts = &(pComm->timeouts); + + if ((pTimeouts->ReadIntervalTimeout == MAXULONG) && + (pTimeouts->ReadTotalTimeoutConstant == MAXULONG)) + { + CommLog_Print( + WLOG_WARN, + "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG"); + SetLastError(ERROR_INVALID_PARAMETER); + goto return_false; + } + + /* VMIN */ + + if ((pTimeouts->ReadIntervalTimeout == MAXULONG) && + (pTimeouts->ReadTotalTimeoutMultiplier == 0) && (pTimeouts->ReadTotalTimeoutConstant == 0)) + { + vmin = 0; + } + else + { + /* N */ + /* vmin = nNumberOfBytesToRead < 256 ? nNumberOfBytesToRead : 255;*/ /* 0xFF */ + /* NB: we might wait endlessly with vmin=N, prefer to + * force vmin=1 and return with bytes + * available. FIXME: is a feature disarded here? */ + vmin = 1; + } + + /* VTIME */ + + if ((pTimeouts->ReadIntervalTimeout > 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG)) + { + /* Ti */ + vtime = _vtime(pTimeouts->ReadIntervalTimeout); + } + + /* TMAX */ + pTmaxTimeout = &tmaxTimeout; + + if ((pTimeouts->ReadIntervalTimeout == MAXULONG) && + (pTimeouts->ReadTotalTimeoutMultiplier == MAXULONG)) + { + /* Tc */ + Tmax = pTimeouts->ReadTotalTimeoutConstant; + } + else + { + /* Tmax */ + Tmax = nNumberOfBytesToRead * pTimeouts->ReadTotalTimeoutMultiplier + + pTimeouts->ReadTotalTimeoutConstant; + + /* INDEFinitely */ + if ((Tmax == 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG) && + (pTimeouts->ReadTotalTimeoutMultiplier == 0)) + pTmaxTimeout = NULL; + } + + if ((currentTermios.c_cc[VMIN] != vmin) || (currentTermios.c_cc[VTIME] != vtime)) + { + currentTermios.c_cc[VMIN] = vmin; + currentTermios.c_cc[VTIME] = vtime; + + if (tcsetattr(pComm->fd, TCSANOW, ¤tTermios) < 0) + { + CommLog_Print(WLOG_WARN, + "CommReadFile failure, could not apply new timeout values: VMIN=%" PRIu8 + ", VTIME=%" PRIu8 "", + vmin, vtime); + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + } + + /* wait indefinitely if pTmaxTimeout is NULL */ + + if (pTmaxTimeout != NULL) + { + ZeroMemory(pTmaxTimeout, sizeof(struct timeval)); + + if (Tmax > 0) /* return immdiately if Tmax == 0 */ + { + pTmaxTimeout->tv_sec = Tmax / 1000; /* s */ + pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */ + } + } + + /* FIXME: had expected eventfd_write() to return EAGAIN when + * there is no eventfd_read() but this not the case. */ + /* discard a possible and no more relevant event */ + eventfd_read(pComm->fd_read_event, NULL); + biggestFd = pComm->fd_read; + + if (pComm->fd_read_event > biggestFd) + biggestFd = pComm->fd_read_event; + + FD_ZERO(&read_set); + WINPR_ASSERT(pComm->fd_read_event < FD_SETSIZE); + WINPR_ASSERT(pComm->fd_read < FD_SETSIZE); + FD_SET(pComm->fd_read_event, &read_set); + FD_SET(pComm->fd_read, &read_set); + nbFds = select(biggestFd + 1, &read_set, NULL, NULL, pTmaxTimeout); + + if (nbFds < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + + if (nbFds == 0) + { + /* timeout */ + SetLastError(ERROR_TIMEOUT); + goto return_false; + } + + /* read_set */ + + if (FD_ISSET(pComm->fd_read_event, &read_set)) + { + eventfd_t event = 0; + + if (eventfd_read(pComm->fd_read_event, &event) < 0) + { + if (errno == EAGAIN) + { + WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */ + /* keep on */ + } + else + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, + "unexpected error on reading fd_read_event, errno=[%d] %s\n", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + /* FIXME: goto return_false ? */ + } + + WINPR_ASSERT(errno == EAGAIN); + } + + if (event == WINPR_PURGE_RXABORT) + { + SetLastError(ERROR_CANCELLED); + goto return_false; + } + + WINPR_ASSERT(event == WINPR_PURGE_RXABORT); /* no other expected event so far */ + } + + if (FD_ISSET(pComm->fd_read, &read_set)) + { + ssize_t nbRead = 0; + nbRead = read(pComm->fd_read, lpBuffer, nNumberOfBytesToRead); + + if (nbRead < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, + "CommReadFile failed, ReadIntervalTimeout=%" PRIu32 + ", ReadTotalTimeoutMultiplier=%" PRIu32 + ", ReadTotalTimeoutConstant=%" PRIu32 " VMIN=%u, VTIME=%u", + pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier, + pTimeouts->ReadTotalTimeoutConstant, currentTermios.c_cc[VMIN], + currentTermios.c_cc[VTIME]); + CommLog_Print( + WLOG_WARN, "CommReadFile failed, nNumberOfBytesToRead=%" PRIu32 ", errno=[%d] %s", + nNumberOfBytesToRead, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + + if (errno == EAGAIN) + { + /* keep on */ + goto return_true; /* expect a read-loop to be implemented on the server side */ + } + else if (errno == EBADF) + { + SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */ + goto return_false; + } + else + { + WINPR_ASSERT(FALSE); + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + } + + if (nbRead == 0) + { + /* termios timeout */ + SetLastError(ERROR_TIMEOUT); + goto return_false; + } + + *lpNumberOfBytesRead = nbRead; + + EnterCriticalSection(&pComm->EventsLock); + if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING) + { + if (pComm->eventChar != '\0' && memchr(lpBuffer, pComm->eventChar, nbRead)) + pComm->PendingEvents |= SERIAL_EV_RXCHAR; + } + LeaveCriticalSection(&pComm->EventsLock); + goto return_true; + } + + WINPR_ASSERT(FALSE); + *lpNumberOfBytesRead = 0; +return_false: + LeaveCriticalSection(&pComm->ReadLock); + return FALSE; +return_true: + LeaveCriticalSection(&pComm->ReadLock); + return TRUE; +} + +/** + * ERRORS: + * ERROR_INVALID_HANDLE + * ERROR_NOT_SUPPORTED + * ERROR_INVALID_PARAMETER + * ERROR_BAD_DEVICE + */ +BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) +{ + WINPR_COMM* pComm = (WINPR_COMM*)hDevice; + struct timeval tmaxTimeout; + struct timeval* pTmaxTimeout = NULL; + EnterCriticalSection(&pComm->WriteLock); /* KISSer by the function's beginning */ + + if (!CommIsHandled(hDevice)) + goto return_false; + + if (lpOverlapped != NULL) + { + SetLastError(ERROR_NOT_SUPPORTED); + goto return_false; + } + + if (lpNumberOfBytesWritten == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */ + goto return_false; + } + + *lpNumberOfBytesWritten = 0; /* will be ajusted if required ... */ + + if (nNumberOfBytesToWrite <= 0) + { + goto return_true; /* FIXME: or FALSE? */ + } + + /* FIXME: had expected eventfd_write() to return EAGAIN when + * there is no eventfd_read() but this not the case. */ + /* discard a possible and no more relevant event */ + eventfd_read(pComm->fd_write_event, NULL); + /* ms */ + ULONGLONG Tmax = nNumberOfBytesToWrite * pComm->timeouts.WriteTotalTimeoutMultiplier + + pComm->timeouts.WriteTotalTimeoutConstant; + /* NB: select() may update the timeout argument to indicate + * how much time was left. Keep the timeout variable out of + * the while() */ + pTmaxTimeout = &tmaxTimeout; + ZeroMemory(pTmaxTimeout, sizeof(struct timeval)); + + if (Tmax > 0) + { + pTmaxTimeout->tv_sec = Tmax / 1000; /* s */ + pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */ + } + else if ((pComm->timeouts.WriteTotalTimeoutMultiplier == 0) && + (pComm->timeouts.WriteTotalTimeoutConstant == 0)) + { + pTmaxTimeout = NULL; + } + + /* else return immdiately */ + + while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite) + { + int biggestFd = -1; + fd_set event_set; + fd_set write_set; + int nbFds = 0; + biggestFd = pComm->fd_write; + + if (pComm->fd_write_event > biggestFd) + biggestFd = pComm->fd_write_event; + + FD_ZERO(&event_set); + FD_ZERO(&write_set); + WINPR_ASSERT(pComm->fd_write_event < FD_SETSIZE); + WINPR_ASSERT(pComm->fd_write < FD_SETSIZE); + FD_SET(pComm->fd_write_event, &event_set); + FD_SET(pComm->fd_write, &write_set); + nbFds = select(biggestFd + 1, &event_set, &write_set, NULL, pTmaxTimeout); + + if (nbFds < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + + if (nbFds == 0) + { + /* timeout */ + SetLastError(ERROR_TIMEOUT); + goto return_false; + } + + /* event_set */ + + if (FD_ISSET(pComm->fd_write_event, &event_set)) + { + eventfd_t event = 0; + + if (eventfd_read(pComm->fd_write_event, &event) < 0) + { + if (errno == EAGAIN) + { + WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */ + /* keep on */ + } + else + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, + "unexpected error on reading fd_write_event, errno=[%d] %s\n", + errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + /* FIXME: goto return_false ? */ + } + + WINPR_ASSERT(errno == EAGAIN); + } + + if (event == WINPR_PURGE_TXABORT) + { + SetLastError(ERROR_CANCELLED); + goto return_false; + } + + WINPR_ASSERT(event == WINPR_PURGE_TXABORT); /* no other expected event so far */ + } + + /* write_set */ + + if (FD_ISSET(pComm->fd_write, &write_set)) + { + ssize_t nbWritten = 0; + nbWritten = write(pComm->fd_write, ((const BYTE*)lpBuffer) + (*lpNumberOfBytesWritten), + nNumberOfBytesToWrite - (*lpNumberOfBytesWritten)); + + if (nbWritten < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, + "CommWriteFile failed after %" PRIu32 + " bytes written, errno=[%d] %s\n", + *lpNumberOfBytesWritten, errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + + if (errno == EAGAIN) + { + /* keep on */ + continue; + } + else if (errno == EBADF) + { + SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */ + goto return_false; + } + else + { + WINPR_ASSERT(FALSE); + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + } + + *lpNumberOfBytesWritten += nbWritten; + } + } /* while */ + + /* FIXME: this call to tcdrain() doesn't look correct and + * might hide a bug but was required while testing a serial + * printer. Its driver was expecting the modem line status + * SERIAL_MSR_DSR true after the sending which was never + * happenning otherwise. A purge was also done before each + * Write operation. The serial port was opened with: + * DesiredAccess=0x0012019F. The printer worked fine with + * mstsc. */ + tcdrain(pComm->fd_write); + +return_true: + LeaveCriticalSection(&pComm->WriteLock); + return TRUE; + +return_false: + LeaveCriticalSection(&pComm->WriteLock); + return FALSE; +} + +#endif /* __linux__ */ |