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);
+}