blob: c3616e4e287044569c527392a743a619d9a65471 [file] [log] [blame]
/*
* 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);
}