view 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 source

/*
 * 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);
}