Mercurial > projects > pwm
diff io.c @ 19:5c6155c8e9b6
Handle signals
Handled signals are generally blocked and only unblocked when doing blocking
I/O, i.e. either when reading commands or printing results. A (possibly
queued) signal will then interrupt I/O and can be dealt with in the main loop.
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Fri, 01 Sep 2017 22:33:41 +0200 |
parents | |
children | ec01c579024a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/io.c Fri Sep 01 22:33:41 2017 +0200 @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "compat.h" + +#ifdef HAVE_ERR_H +#include <err.h> +#endif /* HAVE_ERR_H */ +#include <errno.h> +#include <setjmp.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "io.h" +#include "util.h" +#include "pwm.h" + +static sigjmp_buf signal_env; + +static void +signal_handler(int signal_no) +{ + siglongjmp(signal_env, signal_no); +} + +int +io_gl_complete_nothing(WordCompletion *cpl, void *data, const char *line, + int word_end) +{ + return (0); +} + +enum io_status +io_get_char(const char *prompt, int *cp) +{ + enum io_status retval = IO_OK; + GetLine *gl = NULL; + + gl = new_GetLine(16, 0); + if (gl== NULL) { + err(1, "new_GetLine"); + } + gl_catch_blocked(gl); + + /* prompt with echo off */ + gl_echo_mode(gl, 0); + if ((*cp = gl_query_char(gl, prompt, '\0')) == EOF) { + switch (gl_return_status(gl)) { + case GLR_SIGNAL: + retval = IO_SIGNAL; + break; + case GLR_ERROR: + errx(1, "gl_get_line: %s", + gl_error_message(gl, NULL, 0)); + default: + errx(1, "unknown error in gl_get_line"); + } + } + + /* erase prompt */ + if (io_printf("\r%*s\r", (int)strlen(prompt), "") == IO_SIGNAL) { + retval = IO_SIGNAL; + } + + del_GetLine(gl); + + return (retval); +} + +enum io_status +io_get_line(GetLine *gl, const char *prompt, int with_history, + const char *start_line, int start_pos, size_t buf_size, char *buf) +{ + enum io_status retval = IO_OK; + GetLine *gl_private = NULL; + GlHistoryState state; + char *line; + + if (gl == NULL) { + gl = gl_private = new_GetLine(buf_size - 1, 0); + if (gl_private == NULL) { + err(1, "new_GetLine"); + } + gl_catch_blocked(gl_private); + } + + gl_state_of_history(gl, &state); + gl_toggle_history(gl, with_history); + + line = gl_get_line(gl, prompt, start_line, start_pos); + if (line == NULL) { + switch (gl_return_status(gl)) { + case GLR_BLOCKED: + break; + case GLR_SIGNAL: + retval = IO_SIGNAL; + goto out; + case GLR_EOF: + retval = IO_EOF; + goto out; + case GLR_ERROR: + errx(1, "gl_get_line: %s", + gl_error_message(gl, NULL, 0)); + default: + errx(1, "unknown error in gl_get_line"); + } + } + + if (snprintf(buf, buf_size, "%s", line) >= (int)buf_size) { + retval = IO_TRUNCATED; + } + +out: + if (gl != NULL) { + gl_toggle_history(gl, state.enabled); + } + del_GetLine(gl_private); + + return (retval); +} + +enum io_status +io_get_password(const char *prompt, const char *confirm_prompt, + size_t buf_size, char *buf) +{ + enum io_status retval = IO_OK; + GetLine *gl = NULL; + size_t len; + char *password_buf = NULL; + char *confirm_buf = NULL; + + gl = new_GetLine(buf_size - 1, 0); + if (gl == NULL) { + err(1, "new_GetLine"); + } + /* disable default filename completion */ + gl_customize_completion(gl, NULL, io_gl_complete_nothing); + gl_echo_mode(gl, 0); + + password_buf = xmalloc(buf_size); + + if (io_get_line(gl, prompt, 0, NULL, 0, buf_size, password_buf) == + IO_SIGNAL) { + retval = IO_SIGNAL; + goto out; + } + len = strlen(password_buf); + /* strip trailing newline */ + if ((len > 0) && (password_buf[len - 1] == '\n')) { + password_buf[--len] = '\0'; + } + if (len == 0) { + retval = IO_PASSWORD_EMPTY; + goto out; + } + + if (confirm_prompt != NULL) { + if (io_printf("\n") == IO_SIGNAL) { + retval = IO_SIGNAL; + goto out; + } + + /* confirm new password */ + confirm_buf = xmalloc(buf_size); + if (io_get_line(gl, confirm_prompt, 0, NULL, 0, + buf_size, confirm_buf) == IO_SIGNAL) { + retval = IO_SIGNAL; + goto out; + } + len = strlen(confirm_buf); + /* strip trailing newline */ + if ((len > 0) && (confirm_buf[len - 1] == '\n')) { + confirm_buf[--len] = '\0'; + } + if (strcmp(password_buf, confirm_buf) != 0) { + retval = IO_PASSWORD_MISMATCH; + goto out; + } + } + + strcpy(buf, password_buf); + +out: + if (io_printf("\n") == IO_SIGNAL) { + retval = IO_SIGNAL; + goto out; + } + free(password_buf); + free(confirm_buf); + del_GetLine(gl); + + return (retval); +} + +enum io_status +io_dputs(int fd, const char *s) +{ + struct sigaction action; + struct sigaction oaction; + int signal_no = 0; + const char *p = s; + size_t remaining; + ssize_t n; + + /* install signal handlers */ + action.sa_handler = signal_handler; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + sigaddset(&action.sa_mask, SIGTERM); + sigaddset(&action.sa_mask, SIGHUP); + sigaddset(&action.sa_mask, SIGQUIT); + if ((sigaction(SIGINT, &action, &oaction) != 0) || + (sigaction(SIGTERM, &action, &oaction) != 0) || + (sigaction(SIGHUP, &action, &oaction) != 0) || + (sigaction(SIGQUIT, &action, &oaction) != 0)) { + err(1, "sigaction"); + } + + if ((signal_no = sigsetjmp(signal_env, 1)) != 0) { + /* signal received, signal mask has been restored */ + goto out; + } + + remaining = strlen(s); + while (remaining > 0) { + pwm_unblock_signals(); + n = write(fd, p, remaining); + if ((n < (int)remaining) && (errno != EINTR)) { + err(1, "write"); + } + pwm_block_signals(); + remaining -= MAX(n, 0); + p += MAX(n, 0); + } + +out: + /* restore signal handlers */ + if ((sigaction(SIGINT, &oaction, NULL) != 0) || + (sigaction(SIGTERM, &oaction, NULL) != 0) || + (sigaction(SIGHUP, &oaction, NULL) != 0) || + (sigaction(SIGQUIT, &oaction, NULL) != 0)) { + err(1, "sigaction"); + } + + return ((signal_no == 0) ? IO_OK : IO_SIGNAL); +} + +enum io_status +io_vdprintf(int fd, const char *fmt, va_list args) +{ + enum io_status retval; + char *buf; + + xvasprintf(&buf, fmt, args); + retval = io_dputs(fd, buf); + free(buf); + + return (retval); +} + +enum io_status +io_dprintf(int fd, const char *fmt, ...) +{ + enum io_status retval; + va_list args; + + va_start(args, fmt); + retval = io_vdprintf(fd, fmt, args); + va_end(args); + + return (retval); +} + +enum io_status +io_printf(const char *fmt, ...) +{ + enum io_status retval; + va_list args; + + va_start(args, fmt); + retval = io_vdprintf(STDOUT_FILENO, fmt, args); + va_end(args); + + return (retval); +}