| /* |
| * 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 <string.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| |
| #include "log.h" |
| #include "pipe.h" |
| #include "file.h" |
| #include "io_misc.h" |
| #include "async_handle.h" |
| #include "async_io.h" |
| |
| struct NamedPipeObjectStruct { |
| NamedPipeInputCallback *callback; |
| void *data; |
| |
| int (*createPipe) (NamedPipeObject *obj); |
| int (*monitorPipe) (NamedPipeObject *obj); |
| void (*resetPipe) (NamedPipeObject *obj); |
| void (*releaseResources) (NamedPipeObject *obj); |
| |
| struct { |
| char *path; |
| } host; |
| |
| struct { |
| FileDescriptor descriptor; |
| AsyncHandle monitor; |
| } input; |
| |
| #if defined(__MINGW32__) |
| struct { |
| struct { |
| AsyncHandle monitor; |
| HANDLE event; |
| OVERLAPPED overlapped; |
| } connect; |
| } windows; |
| |
| #endif /* */ |
| }; |
| |
| static void |
| initializeHostPath (NamedPipeObject *obj) { |
| obj->host.path = NULL; |
| } |
| |
| static void |
| deallocateHostPath (NamedPipeObject *obj) { |
| free(obj->host.path); |
| initializeHostPath(obj); |
| } |
| |
| static void |
| removePipe (NamedPipeObject *obj) { |
| unlink(obj->host.path); |
| } |
| |
| static void |
| initializeInputDescriptor (NamedPipeObject *obj) { |
| obj->input.descriptor = INVALID_FILE_DESCRIPTOR; |
| } |
| |
| static void |
| closeInputDescriptor (NamedPipeObject *obj) { |
| closeFileDescriptor(obj->input.descriptor); |
| initializeInputDescriptor(obj); |
| } |
| |
| static void |
| initializeInputMonitor (NamedPipeObject *obj) { |
| obj->input.monitor = NULL; |
| } |
| |
| static void |
| stopInputMonitor (NamedPipeObject *obj) { |
| asyncCancelRequest(obj->input.monitor); |
| initializeInputMonitor(obj); |
| } |
| |
| ASYNC_INPUT_CALLBACK(handleNamedPipeInput) { |
| NamedPipeObject *obj = parameters->data; |
| |
| if (parameters->error) { |
| logMessage(LOG_WARNING, "named pipe input error: %s: %s", |
| obj->host.path, strerror(parameters->error)); |
| } else if (parameters->end) { |
| logMessage(LOG_WARNING, "named pipe end-of-file: %s", obj->host.path); |
| } else { |
| const NamedPipeInputCallbackParameters input = { |
| .buffer = parameters->buffer, |
| .length = parameters->length, |
| .data = obj->data |
| }; |
| |
| return obj->callback(&input); |
| } |
| |
| asyncDiscardHandle(obj->input.monitor); |
| initializeInputMonitor(obj); |
| |
| if (obj->resetPipe) obj->resetPipe(obj); |
| obj->monitorPipe(obj); |
| |
| return 0; |
| } |
| |
| static int |
| monitorInput (NamedPipeObject *obj) { |
| if (!obj->input.monitor) { |
| if (!asyncReadFile(&obj->input.monitor, obj->input.descriptor, 0X1000, handleNamedPipeInput, obj)) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| #if defined(__MINGW32__) |
| static int |
| createWindowsPipe (NamedPipeObject *obj) { |
| obj->windows.connect.monitor = NULL; |
| |
| if ((obj->windows.connect.event = CreateEvent(NULL, TRUE, FALSE, NULL))) { |
| if ((obj->input.descriptor = CreateNamedPipe(obj->host.path, |
| PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, |
| PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, |
| 1, 0, 0, 0, NULL)) != INVALID_HANDLE_VALUE) { |
| logMessage(LOG_DEBUG, "named pipe created: %s: handle=%u", |
| obj->host.path, (unsigned int)obj->input.descriptor); |
| |
| return 1; |
| } else { |
| logWindowsSystemError("CreateNamedPipe"); |
| } |
| |
| CloseHandle(obj->windows.connect.event); |
| } else { |
| logWindowsSystemError("CreateEvent"); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| doWindowsPipeConnected (NamedPipeObject *obj) { |
| return monitorInput(obj); |
| } |
| |
| ASYNC_MONITOR_CALLBACK(handleWindowsPipeConnected) { |
| NamedPipeObject *obj = parameters->data; |
| |
| asyncDiscardHandle(obj->windows.connect.monitor); |
| obj->windows.connect.monitor = NULL; |
| |
| doWindowsPipeConnected(obj); |
| return 0; |
| } |
| |
| static int |
| monitorWindowsPipeConnect (NamedPipeObject *obj) { |
| if (ResetEvent(obj->windows.connect.event)) { |
| ZeroMemory(&obj->windows.connect.overlapped, sizeof(obj->windows.connect.overlapped)); |
| obj->windows.connect.overlapped.hEvent = obj->windows.connect.event; |
| |
| if (ConnectNamedPipe(obj->input.descriptor, &obj->windows.connect.overlapped)) { |
| if (doWindowsPipeConnected(obj)) { |
| return 1; |
| } |
| } else { |
| DWORD error = GetLastError(); |
| |
| if (error == ERROR_PIPE_CONNECTED) { |
| if (doWindowsPipeConnected(obj)) { |
| return 1; |
| } |
| } else if (error == ERROR_IO_PENDING) { |
| if (asyncMonitorFileInput(&obj->windows.connect.monitor, obj->windows.connect.event, handleWindowsPipeConnected, obj)) { |
| return 1; |
| } |
| } else { |
| logWindowsError(error, "ConnectNamedPipe"); |
| } |
| } |
| } else { |
| logWindowsSystemError("ResetEvent"); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| disconnectWindowsPipe (NamedPipeObject *obj) { |
| if (!DisconnectNamedPipe(obj->input.descriptor)) { |
| logWindowsSystemError("DisconnectNamedPipe"); |
| } |
| } |
| |
| static void |
| releaseWindowsResources (NamedPipeObject *obj) { |
| if (obj->windows.connect.monitor) { |
| asyncCancelRequest(obj->windows.connect.monitor); |
| obj->windows.connect.monitor = NULL; |
| } |
| |
| if (obj->windows.connect.event) { |
| CloseHandle(obj->windows.connect.event); |
| obj->windows.connect.event = NULL; |
| } |
| } |
| |
| static void |
| setNamedPipeMethods (NamedPipeObject *obj) { |
| obj->createPipe = createWindowsPipe; |
| obj->monitorPipe = monitorWindowsPipeConnect; |
| obj->resetPipe = disconnectWindowsPipe; |
| obj->releaseResources = releaseWindowsResources; |
| } |
| |
| #elif defined(S_ISFIFO) |
| static int |
| createFifo (NamedPipeObject *obj) { |
| lockUmask(); |
| int result = mkfifo(obj->host.path, 0); |
| unlockUmask(); |
| |
| if ((result == -1) && (errno == EEXIST)) { |
| struct stat fifo; |
| |
| if (lstat(obj->host.path, &fifo) == -1) { |
| logMessage(LOG_ERR, "cannot stat FIFO: %s: %s", |
| obj->host.path, strerror(errno)); |
| } else if (S_ISFIFO(fifo.st_mode)) { |
| result = 0; |
| } |
| } |
| |
| if (result != -1) { |
| lockUmask(); |
| int changed = chmod(obj->host.path, S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH) != -1; |
| unlockUmask(); |
| |
| if (changed) { |
| // open read-write even though we only read to prevent an end-of-file condition |
| if ((obj->input.descriptor = open(obj->host.path, O_RDWR|O_NONBLOCK)) != -1) { |
| logMessage(LOG_DEBUG, "FIFO created: %s: fd=%d", |
| obj->host.path, obj->input.descriptor); |
| |
| setCloseOnExec(obj->input.descriptor, 1); |
| return 1; |
| } else { |
| logMessage(LOG_ERR, "cannot open FIFO: %s: %s", |
| obj->host.path, strerror(errno)); |
| } |
| } else { |
| logMessage(LOG_ERR, "cannot set FIFO permissions: %s: %s", |
| obj->host.path, strerror(errno)); |
| } |
| |
| removePipe(obj); |
| } else { |
| logMessage(LOG_ERR, "cannot create FIFO: %s: %s", |
| obj->host.path, strerror(errno)); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| setNamedPipeMethods (NamedPipeObject *obj) { |
| obj->createPipe = createFifo; |
| } |
| |
| #else /* named pipe functions */ |
| #warning named pipes not supported on this platform |
| |
| static void |
| setNamedPipeMethods (NamedPipeObject *obj) { |
| } |
| #endif /* named pipes functions */ |
| |
| NamedPipeObject * |
| newNamedPipeObject (const char *name, NamedPipeInputCallback *callback, void *data) { |
| NamedPipeObject *obj; |
| |
| if ((obj = malloc(sizeof(*obj)))) { |
| memset(obj, 0, sizeof(*obj)); |
| |
| obj->callback = callback; |
| obj->data = data; |
| |
| obj->createPipe = NULL; |
| obj->monitorPipe = monitorInput; |
| obj->resetPipe = NULL; |
| obj->releaseResources = NULL; |
| setNamedPipeMethods(obj); |
| |
| initializeHostPath(obj); |
| initializeInputDescriptor(obj); |
| initializeInputMonitor(obj); |
| |
| { |
| const char *directory = getNamedPipeDirectory(); |
| |
| obj->host.path = directory? makePath(directory, name): NULL; |
| } |
| |
| if (obj->host.path) { |
| if (!obj->createPipe) { |
| logUnsupportedOperation("create named pipe"); |
| } else if (obj->createPipe(obj)) { |
| if (!obj->monitorPipe) { |
| logUnsupportedOperation("monitor named pipe"); |
| } else if (obj->monitorPipe(obj)) { |
| return obj; |
| } |
| |
| closeInputDescriptor(obj); |
| removePipe(obj); |
| } |
| |
| deallocateHostPath(obj); |
| } |
| |
| free(obj); |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| void |
| destroyNamedPipeObject (NamedPipeObject *obj) { |
| logMessage(LOG_DEBUG, "destroying named pipe: %s", obj->host.path); |
| if (obj->releaseResources) obj->releaseResources(obj); |
| stopInputMonitor(obj); |
| closeInputDescriptor(obj); |
| removePipe(obj); |
| deallocateHostPath(obj); |
| free(obj); |
| } |