Last active
December 16, 2019 21:00
-
-
Save Rahix/62f33484e8d191b5c0f73c3580e1e7c3 to your computer and use it in GitHub Desktop.
echoed - A very questionable daemon for converting upper-case and lower-case
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* echoed - Pipedeamon which changes to lowercase and uppercase | |
* Copyright (C) 2019 Harald Seiler | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU General Public License as published by | |
* the Free Software Foundation, either version 3 of the License, or | |
* (at your option) any later version. | |
* | |
* This program is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
* GNU General Public License for more details. | |
* | |
* You should have received a copy of the GNU General Public License | |
* along with this program. If not, see <https://www.gnu.org/licenses/>. | |
*/ | |
#include <ctype.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <poll.h> | |
#include <signal.h> | |
#include <stdbool.h> | |
#include <stddef.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <sys/signalfd.h> | |
#include <sys/stat.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
#define FIFO_IN_PATH "/tmp/fifo-in" | |
#define FIFO_OUT_PATH "/tmp/fifo-out" | |
static __attribute__((noreturn)) int bail(char *reason); | |
static int echoed_create_fifo(char *name); | |
static int echoed_create_signalfd(void); | |
static int echoed_read_signal(int fd_sig); | |
static int echoed_poll(int fd_sig, int fd_pipe, bool is_output); | |
int main(void) | |
{ | |
int fd_sig, fd_in, fd_out; | |
if (echoed_create_fifo(FIFO_IN_PATH) < 0) | |
bail("Failed to create read fifo"); | |
if (echoed_create_fifo(FIFO_OUT_PATH) < 0) | |
bail("Failed to create write fifo"); | |
if ((fd_sig = echoed_create_signalfd()) < 0) | |
bail("Failed to initialize signalfd"); | |
/* | |
* Open the pipes as read-write. This has two advantages: | |
* | |
* 1. There will always be a reader/writer open for the pipe which means | |
* that we will never run into a SIGPIPE on the output pipe or EOF on | |
* the input pipe. | |
* 2. Because we want to open them with O_NONBLOCK, if no reader were | |
* open for the output pipe, open() would fail with ENXIO (see fifo(7)). | |
* Opening read-write circumvents this issue (Linux specific). | |
*/ | |
if ((fd_in = open(FIFO_IN_PATH, O_RDWR | O_NONBLOCK)) < 0) | |
bail("Failed to open read fifo"); | |
if ((fd_out = open(FIFO_OUT_PATH, O_RDWR | O_NONBLOCK)) < 0) | |
bail("Failed to open write fifo"); | |
char buf[1024], *bufptr; | |
int (*converter_func)(int) = toupper; | |
ssize_t bytes = 0; | |
while (1) { | |
int ev; | |
if (bytes > 0) { | |
/* Some data left, which needs to be flushed. */ | |
ev = echoed_poll(fd_sig, fd_out, true); | |
} else { | |
/* Wait for new data to come in. */ | |
ev = echoed_poll(fd_sig, fd_in, false); | |
} | |
if (ev == fd_sig) { | |
int signo = echoed_read_signal(fd_sig); | |
switch (signo) { | |
case SIGINT: | |
goto exit; | |
case SIGUSR1: | |
converter_func = toupper; | |
break; | |
case SIGUSR2: | |
converter_func = tolower; | |
break; | |
default: | |
bail("Catched an unknown signal"); | |
} | |
} else if (ev == fd_out && bytes > 0) { | |
ssize_t written = write(fd_out, buf, bytes); | |
if (written < 0) | |
bail("Failed writing to fifo"); | |
bufptr += written; | |
bytes -=written; | |
} else if (ev == fd_in && bytes == 0) { | |
bytes = read(fd_in, buf, sizeof(buf)); | |
if (bytes < 0) | |
bail("Failed to read from fifo"); | |
for (ssize_t i = 0; i < bytes; i++) { | |
buf[i] = converter_func(buf[i]); | |
} | |
bufptr = buf; | |
} | |
} | |
exit: | |
unlink(FIFO_IN_PATH); | |
unlink(FIFO_OUT_PATH); | |
exit(EXIT_SUCCESS); | |
} | |
/* | |
* Abort because of an unrecoverable error. | |
*/ | |
static __attribute__((noreturn)) int bail(char *reason) | |
{ | |
char buf[512]; | |
snprintf(buf, sizeof(buf), "echoed: %s", reason); | |
perror(buf); | |
exit(EXIT_FAILURE); | |
} | |
/* | |
* Create a named pipe. In case the file previously existed, remove it and | |
* retry. | |
*/ | |
static int echoed_create_fifo(char *name) | |
{ | |
if (mkfifo(name, 0666) < 0) { | |
if (errno == EEXIST) { | |
/* | |
* If the named fifo already exists, remove it and try | |
* again. | |
*/ | |
unlink(name); | |
return mkfifo(name, 0666); | |
} | |
return -1; | |
} | |
return 0; | |
} | |
/* | |
* Initialize a signalfd(2). We will use it to monitor for: | |
* | |
* - SIGINT: Keyboard-Interrupt; cleanup & exit | |
* - SIGUSR1: Change to uppercase mode | |
* - SIGUSR2: Change to lowercase mode | |
*/ | |
static int echoed_create_signalfd(void) | |
{ | |
sigset_t mask; | |
sigemptyset(&mask); | |
sigaddset(&mask, SIGINT); | |
sigaddset(&mask, SIGUSR1); | |
sigaddset(&mask, SIGUSR2); | |
/* Block default handlers so they won't get triggered */ | |
if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { | |
return -1; | |
} | |
return signalfd(-1, &mask, 0); | |
} | |
/* | |
* Read a signal from the signalfd and returns its signal number. | |
*/ | |
static int echoed_read_signal(int fd_sig) { | |
struct signalfd_siginfo fdsi; | |
if (read(fd_sig, &fdsi, sizeof(fdsi)) != sizeof(fdsi)) | |
bail("signalfd read wrong size"); | |
return fdsi.ssi_signo; | |
} | |
/* | |
* Poll for either a signal or the pipe. If `is_output` is true, wait for | |
* fd_pipe to become writable otherwise wait for it to become readable. | |
* | |
* Returns the file descriptor which triggered the event. | |
*/ | |
static int echoed_poll(int fd_sig, int fd_pipe, bool is_output) | |
{ | |
struct pollfd poll_fds[] = { | |
{.fd = fd_sig, .events = POLLIN}, | |
{.fd = fd_pipe, .events = is_output ? POLLOUT : POLLIN}, | |
}; | |
int ret = poll(poll_fds, sizeof(poll_fds) / sizeof(struct pollfd), -1); | |
if (ret < 0) | |
bail("Poll failed"); | |
if (poll_fds[0].revents & POLLIN) { | |
return fd_sig; | |
} else if (poll_fds[1].revents & (POLLIN | POLLOUT)) { | |
return fd_pipe; | |
} | |
bail("Poll did not return any event"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment