Skip to content

Instantly share code, notes, and snippets.

@Rahix
Last active December 16, 2019 21:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rahix/62f33484e8d191b5c0f73c3580e1e7c3 to your computer and use it in GitHub Desktop.
Save Rahix/62f33484e8d191b5c0f73c3580e1e7c3 to your computer and use it in GitHub Desktop.
echoed - A very questionable daemon for converting upper-case and lower-case
/*
* 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