| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| |
| #include "io_misc.h" |
| #include "log.h" |
| #include "file.h" |
| #include "async_handle.h" |
| #include "async_wait.h" |
| #include "async_io.h" |
| |
| typedef struct InputOutputMethodsStruct InputOutputMethods; |
| |
| typedef struct { |
| const InputOutputMethods *methods; |
| |
| union { |
| FileDescriptor file; |
| SocketDescriptor socket; |
| } descriptor; |
| } InputOutputHandle; |
| |
| typedef struct { |
| unsigned ready:1; |
| } InputOutputMonitor; |
| |
| typedef int MonitorInputOutputMethod (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom); |
| typedef ssize_t ReadDataMethod (const InputOutputHandle *ioh, void *buffer, size_t size); |
| typedef ssize_t WriteDataMethod (const InputOutputHandle *ioh, const void *buffer, size_t size); |
| |
| struct InputOutputMethodsStruct { |
| MonitorInputOutputMethod *monitorInput; |
| MonitorInputOutputMethod *monitorOutput; |
| MonitorInputOutputMethod *monitorAlert; |
| |
| ReadDataMethod *readData; |
| WriteDataMethod *writeData; |
| }; |
| |
| ASYNC_MONITOR_CALLBACK(setInputOutputMonitor) { |
| InputOutputMonitor *iom = parameters->data; |
| |
| iom->ready = 1; |
| return 0; |
| } |
| |
| ASYNC_CONDITION_TESTER(testInputOutputMonitor) { |
| InputOutputMonitor *iom = data; |
| |
| return iom->ready; |
| } |
| |
| static int |
| awaitInputOutput (const InputOutputHandle *ioh, int timeout, MonitorInputOutputMethod *monitorInputOutput) { |
| InputOutputMonitor iom = { |
| .ready = 0 |
| }; |
| |
| AsyncHandle monitor; |
| |
| if (monitorInputOutput(ioh, &monitor, &iom)) { |
| asyncAwaitCondition(timeout, testInputOutputMonitor, &iom); |
| asyncCancelRequest(monitor); |
| if (iom.ready) return 1; |
| |
| #ifdef ETIMEDOUT |
| errno = ETIMEDOUT; |
| #else /* ETIMEDOUT */ |
| errno = EAGAIN; |
| #endif /* ETIMEDOUT */ |
| } |
| |
| return 0; |
| } |
| |
| static int |
| awaitInput (const InputOutputHandle *ioh, int timeout) { |
| return awaitInputOutput(ioh, timeout, ioh->methods->monitorInput); |
| } |
| |
| static int |
| awaitOutput (const InputOutputHandle *ioh, int timeout) { |
| return awaitInputOutput(ioh, timeout, ioh->methods->monitorOutput); |
| } |
| |
| static int |
| awaitAlert (const InputOutputHandle *ioh, int timeout) { |
| return awaitInputOutput(ioh, timeout, ioh->methods->monitorAlert); |
| } |
| |
| static ssize_t |
| readData ( |
| const InputOutputHandle *ioh, |
| void *buffer, size_t size, |
| int initialTimeout, int subsequentTimeout |
| ) { |
| unsigned char *address = buffer; |
| |
| #ifdef __MSDOS__ |
| int tried = 0; |
| goto noInput; |
| #endif /* __MSDOS__ */ |
| |
| while (size > 0) { |
| ssize_t count = ioh->methods->readData(ioh, address, size); |
| |
| #ifdef __MSDOS__ |
| tried = 1; |
| #endif /* __MSDOS__ */ |
| |
| if (count == -1) { |
| if (errno == EINTR) continue; |
| if (errno == EAGAIN) goto noInput; |
| |
| #ifdef EWOULDBLOCK |
| if (errno == EWOULDBLOCK) goto noInput; |
| #endif /* EWOULDBLOCK */ |
| |
| logSystemError("read"); |
| return count; |
| } |
| |
| if (!count) { |
| unsigned char *start; |
| unsigned int offset; |
| int timeout; |
| |
| noInput: |
| start = buffer; |
| offset = address - start; |
| timeout = offset? subsequentTimeout: initialTimeout; |
| |
| if (timeout) { |
| if (awaitInput(ioh, timeout)) continue; |
| } else |
| |
| #ifdef __MSDOS__ |
| if (!tried) { |
| if (awaitInput(ioh, 0)) continue; |
| } else |
| #endif /* __MSDOS__ */ |
| |
| { |
| errno = EAGAIN; |
| } |
| |
| break; |
| } |
| |
| address += count; |
| size -= count; |
| } |
| |
| { |
| unsigned char *start = buffer; |
| |
| return address - start; |
| } |
| } |
| |
| static ssize_t |
| writeData (const InputOutputHandle *ioh, const void *buffer, size_t size) { |
| const unsigned char *address = buffer; |
| |
| canWrite: |
| while (size > 0) { |
| ssize_t count = ioh->methods->writeData(ioh, address, size); |
| |
| if (count == -1) { |
| if (errno == EINTR) continue; |
| if (errno == EAGAIN) goto noOutput; |
| |
| #ifdef EWOULDBLOCK |
| if (errno == EWOULDBLOCK) goto noOutput; |
| #endif /* EWOULDBLOCK */ |
| |
| logSystemError("Write"); |
| return count; |
| } |
| |
| if (!count) { |
| errno = EAGAIN; |
| |
| noOutput: |
| do { |
| if (awaitOutput(ioh, 15000)) goto canWrite; |
| } while (errno == EAGAIN); |
| |
| return -1; |
| } |
| |
| address += count; |
| size -= count; |
| } |
| |
| { |
| const unsigned char *start = buffer; |
| return address - start; |
| } |
| } |
| |
| static int |
| monitorFileInput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) { |
| return asyncMonitorFileInput(handle, ioh->descriptor.file, setInputOutputMonitor, iom); |
| } |
| |
| static int |
| monitorFileOutput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) { |
| return asyncMonitorFileOutput(handle, ioh->descriptor.file, setInputOutputMonitor, iom); |
| } |
| |
| static int |
| monitorFileAlert (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) { |
| return asyncMonitorFileAlert(handle, ioh->descriptor.file, setInputOutputMonitor, iom); |
| } |
| |
| static ssize_t |
| readFileData (const InputOutputHandle *ioh, void *buffer, size_t size) { |
| return readFileDescriptor(ioh->descriptor.file, buffer, size); |
| } |
| |
| static ssize_t |
| writeFileData (const InputOutputHandle *ioh, const void *buffer, size_t size) { |
| return writeFileDescriptor(ioh->descriptor.file, buffer, size); |
| } |
| |
| static const InputOutputMethods fileMethods = { |
| .monitorInput = monitorFileInput, |
| .monitorOutput = monitorFileOutput, |
| .monitorAlert = monitorFileAlert, |
| |
| .readData = readFileData, |
| .writeData = writeFileData |
| }; |
| |
| static void |
| makeFileHandle (InputOutputHandle *ioh, FileDescriptor fileDescriptor) { |
| ioh->methods = &fileMethods; |
| ioh->descriptor.file = fileDescriptor; |
| } |
| |
| void |
| closeFile (FileDescriptor *fileDescriptor) { |
| if (*fileDescriptor != INVALID_FILE_DESCRIPTOR) { |
| closeFileDescriptor(*fileDescriptor); |
| *fileDescriptor = INVALID_FILE_DESCRIPTOR; |
| } |
| } |
| |
| int |
| awaitFileInput (FileDescriptor fileDescriptor, int timeout) { |
| InputOutputHandle ioh; |
| |
| makeFileHandle(&ioh, fileDescriptor); |
| return awaitInput(&ioh, timeout); |
| } |
| |
| int |
| awaitFileOutput (FileDescriptor fileDescriptor, int timeout) { |
| InputOutputHandle ioh; |
| |
| makeFileHandle(&ioh, fileDescriptor); |
| return awaitOutput(&ioh, timeout); |
| } |
| |
| int |
| awaitFileAlert (FileDescriptor fileDescriptor, int timeout) { |
| InputOutputHandle ioh; |
| |
| makeFileHandle(&ioh, fileDescriptor); |
| return awaitAlert(&ioh, timeout); |
| } |
| |
| ssize_t |
| readFile ( |
| FileDescriptor fileDescriptor, void *buffer, size_t size, |
| int initialTimeout, int subsequentTimeout |
| ) { |
| InputOutputHandle ioh; |
| |
| makeFileHandle(&ioh, fileDescriptor); |
| return readData(&ioh, buffer, size, initialTimeout, subsequentTimeout); |
| } |
| |
| ssize_t |
| writeFile (FileDescriptor fileDescriptor, const void *buffer, size_t size) { |
| InputOutputHandle ioh; |
| |
| makeFileHandle(&ioh, fileDescriptor); |
| return writeData(&ioh, buffer, size); |
| } |
| |
| #ifdef GOT_SOCKETS |
| static int |
| monitorSocketInput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) { |
| return asyncMonitorSocketInput(handle, ioh->descriptor.socket, setInputOutputMonitor, iom); |
| } |
| |
| static int |
| monitorSocketOutput (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) { |
| return asyncMonitorSocketOutput(handle, ioh->descriptor.socket, setInputOutputMonitor, iom); |
| } |
| |
| static int |
| monitorSocketAlert (const InputOutputHandle *ioh, AsyncHandle *handle, InputOutputMonitor *iom) { |
| return asyncMonitorSocketAlert(handle, ioh->descriptor.socket, setInputOutputMonitor, iom); |
| } |
| |
| static ssize_t |
| readSocketData (const InputOutputHandle *ioh, void *buffer, size_t size) { |
| return readSocketDescriptor(ioh->descriptor.socket, buffer, size); |
| } |
| |
| static ssize_t |
| writeSocketData (const InputOutputHandle *ioh, const void *buffer, size_t size) { |
| return writeSocketDescriptor(ioh->descriptor.socket, buffer, size); |
| } |
| |
| static const InputOutputMethods socketMethods = { |
| .monitorInput = monitorSocketInput, |
| .monitorOutput = monitorSocketOutput, |
| .monitorAlert = monitorSocketAlert, |
| |
| .readData = readSocketData, |
| .writeData = writeSocketData |
| }; |
| |
| static void |
| makeSocketHandle (InputOutputHandle *ioh, SocketDescriptor socketDescriptor) { |
| ioh->methods = &socketMethods; |
| ioh->descriptor.socket = socketDescriptor; |
| } |
| |
| void |
| closeSocket (SocketDescriptor *socketDescriptor) { |
| if (*socketDescriptor != INVALID_SOCKET_DESCRIPTOR) { |
| closeSocketDescriptor(*socketDescriptor); |
| *socketDescriptor = INVALID_SOCKET_DESCRIPTOR; |
| } |
| } |
| |
| int |
| awaitSocketInput (SocketDescriptor socketDescriptor, int timeout) { |
| InputOutputHandle ioh; |
| |
| makeSocketHandle(&ioh, socketDescriptor); |
| return awaitInput(&ioh, timeout); |
| } |
| |
| int |
| awaitSocketOutput (SocketDescriptor socketDescriptor, int timeout) { |
| InputOutputHandle ioh; |
| |
| makeSocketHandle(&ioh, socketDescriptor); |
| return awaitOutput(&ioh, timeout); |
| } |
| |
| int |
| awaitSocketAlert (SocketDescriptor socketDescriptor, int timeout) { |
| InputOutputHandle ioh; |
| |
| makeSocketHandle(&ioh, socketDescriptor); |
| return awaitAlert(&ioh, timeout); |
| } |
| |
| ssize_t |
| readSocket ( |
| SocketDescriptor socketDescriptor, void *buffer, size_t size, |
| int initialTimeout, int subsequentTimeout |
| ) { |
| InputOutputHandle ioh; |
| |
| makeSocketHandle(&ioh, socketDescriptor); |
| return readData(&ioh, buffer, size, initialTimeout, subsequentTimeout); |
| } |
| |
| ssize_t |
| writeSocket (SocketDescriptor socketDescriptor, const void *buffer, size_t size) { |
| InputOutputHandle ioh; |
| |
| makeSocketHandle(&ioh, socketDescriptor); |
| return writeData(&ioh, buffer, size); |
| } |
| |
| int |
| connectSocket ( |
| SocketDescriptor socketDescriptor, |
| const struct sockaddr *address, |
| size_t addressLength, |
| int timeout |
| ) { |
| int result = connect(socketDescriptor, address, addressLength); |
| |
| if (result == -1) { |
| #ifdef EINPROGRESS |
| if (getSocketError() == EINPROGRESS) { |
| if (awaitSocketOutput(socketDescriptor, timeout)) { |
| int error; |
| socklen_t length = sizeof(error); |
| |
| if (getsockopt(socketDescriptor, SOL_SOCKET, SO_ERROR, (void *)&error, &length) != -1) { |
| if (!error) return 0; |
| errno = error; |
| } |
| } |
| } |
| #endif /* EINPROGRESS */ |
| } |
| |
| return result; |
| } |
| |
| int |
| setSocketLingerTime (SocketDescriptor socketDescriptor, int seconds) { |
| struct linger linger = { |
| .l_onoff = 1, |
| .l_linger = seconds |
| }; |
| |
| if (setsockopt(socketDescriptor, SOL_SOCKET, SO_LINGER, (const void *)&linger, sizeof(linger)) != -1) return 1; |
| logSystemError("setsockopt[SO_LINGER]"); |
| return 0; |
| } |
| |
| int |
| setSocketNoLinger (SocketDescriptor socketDescriptor) { |
| return setSocketLingerTime(socketDescriptor, 0); |
| } |
| |
| #else /* have sockets */ |
| #warning sockets not supported on this platform |
| #endif /* GOT_SOCKETS */ |
| |
| int |
| changeOpenFlags (FileDescriptor fileDescriptor, int flagsToClear, int flagsToSet) { |
| #if defined(F_GETFL) && defined(F_SETFL) |
| int flags; |
| |
| if ((flags = fcntl(fileDescriptor, F_GETFL)) != -1) { |
| flags &= ~flagsToClear; |
| flags |= flagsToSet; |
| if (fcntl(fileDescriptor, F_SETFL, flags) != -1) { |
| return 1; |
| } else { |
| logSystemError("F_SETFL"); |
| } |
| } else { |
| logSystemError("F_GETFL"); |
| } |
| #else /* defined(F_GETFL) && defined(F_SETFL) */ |
| errno = ENOSYS; |
| #endif /* defined(F_GETFL) && defined(F_SETFL) */ |
| |
| return 0; |
| } |
| |
| int |
| setOpenFlags (FileDescriptor fileDescriptor, int state, int flags) { |
| if (state) { |
| return changeOpenFlags(fileDescriptor, 0, flags); |
| } else { |
| return changeOpenFlags(fileDescriptor, flags, 0); |
| } |
| } |
| |
| int |
| setBlockingIo (FileDescriptor fileDescriptor, int state) { |
| #ifdef O_NONBLOCK |
| if (setOpenFlags(fileDescriptor, !state, O_NONBLOCK)) return 1; |
| #else /* O_NONBLOCK */ |
| errno = ENOSYS; |
| #endif /* O_NONBLOCK */ |
| |
| return 0; |
| } |
| |
| int |
| setCloseOnExec (FileDescriptor fileDescriptor, int state) { |
| #if defined(F_SETFD) && defined(FD_CLOEXEC) |
| if (fcntl(fileDescriptor, F_SETFD, (state? FD_CLOEXEC: 0)) != -1) return 1; |
| #else /* defined(F_SETFD) && defined(FD_CLOEXEC) */ |
| errno = ENOSYS; |
| #endif /* defined(F_SETFD) && defined(FD_CLOEXEC) */ |
| |
| return 0; |
| } |