changeset 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 1e39a251cbe9
children efef93e54c5f
files Makefile cmd.c cmd.h compat.h compat/closefrom.c compat/closefrom.h io.c io.h pager.c pager.h proc.c proc.h pwm.c pwm.h
diffstat 14 files changed, 810 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu Aug 24 13:10:56 2017 +0200
+++ b/Makefile	Fri Sep 01 22:33:41 2017 +0200
@@ -79,6 +79,7 @@
 ifeq ($(OS_NAME),Linux)
   HAVE_ARC4RANDOM ?=	0
   HAVE_ASPRINTF ?=	1
+  HAVE_CLOSEFROM ?=	0
   HAVE_ERR_H ?=		1
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	0
@@ -87,6 +88,7 @@
 else ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly),)
   HAVE_ARC4RANDOM ?=	1
   HAVE_ASPRINTF ?=	1
+  HAVE_CLOSEFROM ?=	1
   HAVE_ERR_H ?=		1
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	1
@@ -95,6 +97,7 @@
 else ifeq ($(OS_NAME),NetBSD)
   HAVE_ARC4RANDOM ?=	1
   HAVE_ASPRINTF ?=	1
+  HAVE_CLOSEFROM ?=	1
   HAVE_ERR_H ?=		1
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	1
@@ -103,6 +106,7 @@
 else ifeq ($(OS_NAME),OpenBSD)
   HAVE_ARC4RANDOM ?=	1
   HAVE_ASPRINTF ?=	1
+  HAVE_CLOSEFROM ?=	1
   HAVE_ERR_H ?=		1
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	1
@@ -120,12 +124,14 @@
     HAVE_ERR_H ?=	1
     HAVE_GETRANDOM ?=	1
   endif
+  HAVE_CLOSEFROM ?=	1
   HAVE_SYS_QUEUE_H ?=	0
   HAVE_SYS_TREE_H ?=	0
   HAVE_SETPROGNAME ?=	0
 else
   HAVE_ARC4RANDOM ?=	0
   HAVE_ASPRINTF ?=	0
+  HAVE_CLOSEFROM ?=	0
   HAVE_ERR_H ?=		0
   HAVE_GETRANDOM ?=	0
   HAVE_SYS_QUEUE_H ?=	0
@@ -134,7 +140,9 @@
 endif
 
 OBJS =	cmd.o \
+	io.o \
 	pager.o \
+	proc.o \
 	pw.o \
 	pwfile.o \
 	pwm.o \
@@ -166,6 +174,11 @@
 else
   OBJS +=	rand-dev-random.o
 endif
+ifeq ($(HAVE_CLOSEFROM),1)
+  XCPPFLAGS +=	-DHAVE_CLOSEFROM
+else
+  OBJS +=	compat/closefrom.o
+endif
 ifeq ($(HAVE_ERR_H),1)
   XCPPFLAGS +=	-DHAVE_ERR_H
 else
--- a/cmd.c	Thu Aug 24 13:10:56 2017 +0200
+++ b/cmd.c	Fri Sep 01 22:33:41 2017 +0200
@@ -36,7 +36,9 @@
 #include <unistd.h>
 
 #include "cmd.h"
+#include "io.h"
 #include "pager.h"
+#include "proc.h"
 #include "pw.h"
 #include "pwfile.h"
 #include "util.h"
@@ -222,10 +224,14 @@
 	}
 
 	if (ctx->errmsg != NULL) {
-		printf("%s\n", ctx->errmsg);
+		if (io_printf("%s\n", ctx->errmsg) == IO_SIGNAL) {
+			return (CMD_SIGNAL);
+		}
 	}
-	printf("There are%sunsaved changes\n", ctx->unsaved_changes ? " " :
-	    " no ");
+	if (io_printf("There are%sunsaved changes\n",
+	    ctx->unsaved_changes ? " " : " no ") == IO_SIGNAL) {
+		return (CMD_SIGNAL);
+	}
 
 	return (CMD_STATUS);
 }
@@ -233,6 +239,7 @@
 static enum cmd_return
 cmd_info(struct pwm_ctx *ctx, int argc, char *argv[])
 {
+	enum cmd_return	retval;
 	struct metadata	*metadata;
 	struct pager	*pager;
 	struct tm	*tm;
@@ -244,7 +251,7 @@
 
 	metadata = pwfile_get_metadata(ctx);
 
-	pager = pager_create(stdout);
+	pager = pager_create(STDOUT_FILENO);
 	pager_printf(pager, "Format:      0x%04x\n", metadata->version);
 	if (metadata->user != NULL) {
 		pager_printf(pager, "User:        %s\n", metadata->user);
@@ -258,12 +265,12 @@
 	tm = gmtime(&metadata->timestamp);
 	strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm);
 	pager_printf(pager, "Last Saved:  %s\n", timebuf);
-	pager_show(pager);
+	retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
 	pager_destroy(pager);
 
 	pwfile_destroy_metadata(metadata);
 
-	return (CMD_OK);
+	return (retval);
 }
 
 static enum cmd_return
@@ -338,7 +345,7 @@
 		}
 	}
 
-	pager = pager_create(stdout);
+	pager = pager_create(STDOUT_FILENO);
 	list = pwfile_create_list(ctx);
 	for (j = 0; list[j] != NULL; j++) {
 		if (list[j]->any.type == ITEM_TYPE_GROUP) {
@@ -363,9 +370,7 @@
 			pwfile_destroy_record(record);
 		}
 	}
-	pager_show(pager);
-
-	retval = CMD_OK;
+	retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
 
 out:
 	pager_destroy(pager);
@@ -633,12 +638,11 @@
 			pwm_err(ctx, "record %u does not exist", id);
 			goto out;
 		}
+		retval = CMD_OK;
 	} else {
-		printf("%s\n", password);
+		retval = io_printf("%s\n", password);
 	}
 
-	retval = CMD_OK;
-
 out:
 	free(char_groupv);
 
@@ -667,14 +671,15 @@
 	return (CMD_OK);
 }
 
-static void
-print_record(struct record *record, int fields[], int show_labels, FILE *fp)
+static int
+print_record(struct record *record, int fields[], int show_labels, int fd)
 {
 	struct pager	*pager;
 	struct tm	*tm;
 	char		timebuf[TIME_SIZE];
+	int		retval;
 
-	pager = pager_create(fp);
+	pager = pager_create(fd);
 	if (fields[FIELD_TITLE]) {
 		pager_printf(pager, "%s%s\n", show_labels ?
 		    field_labels[FIELD_TITLE] : "", (record->title != NULL) ?
@@ -717,13 +722,16 @@
 		pager_printf(pager, "%s%s\n", show_labels ?
 		    field_labels[FIELD_MTIME] : "", timebuf);
 	}
-	pager_show(pager);
+	retval = pager_show(pager);
 	pager_destroy(pager);
+
+	return (retval);
 }
 
 static enum cmd_return
 cmd_show(struct pwm_ctx *ctx, int argc, char *argv[])
 {
+	enum cmd_return	retval;
 	unsigned int	id;
 	struct record	*record;
 	int		i;
@@ -767,10 +775,11 @@
 		pwm_err(ctx, "record %u does not exist", id);
 		return (CMD_ERR);
 	}
-	print_record(record, fields, 1, stdout);
+	retval = (print_record(record, fields, 1, STDOUT_FILENO) != IO_SIGNAL) ?
+	    CMD_OK : CMD_SIGNAL;
 	pwfile_destroy_record(record);
 
-	return (CMD_OK);
+	return (retval);
 }
 
 static enum cmd_return
@@ -781,7 +790,7 @@
 	struct record	*record = NULL;
 	enum field_type	type;
 	int		fields[COUNTOF(field_namev) - 1] = { 0 };
-	FILE		*fp = NULL;
+	struct proc	proc = { 0 };
 
 	if (argc != 4) {
 		return (CMD_USAGE);
@@ -799,9 +808,7 @@
 	}
 	fields[type] = 1;
 
-	fp = popen(argv[3], "w");
-	if (fp == NULL) {
-		warn("popen");
+	if (proc_open(&proc, argv[3], "w") != IO_OK) {
 		goto out;
 	}
 
@@ -811,14 +818,15 @@
 		goto out;
 	}
 
-	print_record(record, fields, 0, fp);
-
-	retval = CMD_OK;
+	retval = (print_record(record, fields, 0, proc.fd) != IO_SIGNAL) ?
+	    CMD_OK : CMD_SIGNAL;
 
 out:
 	pwfile_destroy_record(record);
-	if (fp != NULL) {
-		pclose(fp);
+	if (proc.pid != 0) {
+		if (proc_close(&proc) == IO_SIGNAL) {
+			retval = CMD_SIGNAL;
+		}
 	}
 
 	return (retval);
@@ -883,6 +891,7 @@
 static enum cmd_return
 cmd_help(struct pwm_ctx *ctx, int argc, char *argv[])
 {
+	enum cmd_return	retval = CMD_OK;
 	struct pager	*pager;
 	struct cmd	*cmd;
 
@@ -890,7 +899,7 @@
 		return (CMD_USAGE);
 	}
 
-	pager = pager_create(stdout);
+	pager = pager_create(STDOUT_FILENO);
 	if (argc == 2) {
 		for (cmd = cmds; cmd->cmd_func != NULL; cmd++) {
 			if ((strcmp(argv[1], cmd->abbrev_cmd) == 0) ||
@@ -906,10 +915,10 @@
 			    cmd->full_cmd, cmd->description);
 		}
 	}
-	pager_show(pager);
+	retval = (pager_show(pager) != IO_SIGNAL) ? CMD_OK : CMD_SIGNAL;
 	pager_destroy(pager);
 
-	return (CMD_OK);
+	return (retval);
 }
 
 static enum cmd_return
--- a/cmd.h	Thu Aug 24 13:10:56 2017 +0200
+++ b/cmd.h	Fri Sep 01 22:33:41 2017 +0200
@@ -31,6 +31,7 @@
 	CMD_STATUS,
 	CMD_ERR,
 	CMD_USAGE,
+	CMD_SIGNAL,
 	CMD_QUIT
 };
 
--- a/compat.h	Thu Aug 24 13:10:56 2017 +0200
+++ b/compat.h	Fri Sep 01 22:33:41 2017 +0200
@@ -31,6 +31,10 @@
 #include "compat/asprintf.h"
 #endif /* !HAVE_ASPRINTF */
 
+#ifndef	HAVE_CLOSEFROM
+#include "compat/closefrom.h"
+#endif /* !HAVE_CLOSEFROM */
+
 #ifndef	HAVE_ERR_H
 #include "compat/err.h"
 #endif /* !HAVE_ERR_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/closefrom.c	Fri Sep 01 22:33:41 2017 +0200
@@ -0,0 +1,46 @@
+/*
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+int
+closefrom(int fd_min)
+{
+#ifdef	F_CLOSEM
+	return fcntl(fd_min, F_CLOSEM, 0);
+#else /* !F_CLOSEM */
+	int	fd_max;
+	int	fd;
+
+	fd_max = sysconf(_SC_OPEN_MAX);
+	for (fd = fd_min; fd < fd_max; fd++) {
+		if ((close(fd) < 0) && (errno != EBADF)) {
+			return (-1);
+		}
+	}
+
+	return (0);
+#endif /* !F_CLOSEM */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/compat/closefrom.h	Fri Sep 01 22:33:41 2017 +0200
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#ifndef	CLOSEFROM_H
+#define	CLOSEFROM_H
+
+int	closefrom(int);
+
+#endif /* !CLOSEFROM_H */
--- /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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/io.h	Fri Sep 01 22:33:41 2017 +0200
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef	IO_H
+#define	IO_H
+
+#include <libtecla.h>
+#include <stdarg.h>
+
+enum io_status {
+	IO_OK,
+	IO_EOF = -1,
+	IO_ERROR = -2,
+	IO_TRUNCATED = -3,
+	IO_SIGNAL = -4,
+	IO_PASSWORD_EMPTY = -5,
+	IO_PASSWORD_MISMATCH = -6
+};
+
+int		io_gl_complete_nothing(WordCompletion *, void *, const char *,
+    int);
+enum io_status	io_get_char(const char *, int *);
+enum io_status	io_get_line(GetLine *, const char *, int, const char *, int,
+    size_t, char *);
+enum io_status	io_get_password(const char *, const char *, size_t, char *);
+enum io_status	io_dputs(int, const char *);
+enum io_status	io_vdprintf(int, const char *, va_list);
+enum io_status	io_dprintf(int, const char *, ...);
+enum io_status	io_printf(const char *, ...);
+
+#endif /* !IO_H */
--- a/pager.c	Thu Aug 24 13:10:56 2017 +0200
+++ b/pager.c	Fri Sep 01 22:33:41 2017 +0200
@@ -28,7 +28,6 @@
 #endif /* HAVE_ERR_H */
 #include <errno.h>
 #include <fcntl.h>
-#include <libtecla.h>
 #include <limits.h>
 #include <signal.h>
 #include <stdio.h>
@@ -49,7 +48,7 @@
 
 struct pager {
 	TAILQ_HEAD(lines_head, lines_entry) lines_head;
-	FILE		*fp;
+	int		fd;
 	size_t		buf_size;
 	char		*buf;
 };
@@ -59,32 +58,14 @@
 	char		*line;
 };
 
-static int
-getch_prompt(GetLine *gl, const char *prompt)
-{
-	int	c;
-	int	saved_echo_mode;
-
-	/* prompt with echo off */
-	saved_echo_mode = gl_echo_mode(gl, -1);
-	gl_echo_mode(gl, 0);
-	c = gl_query_char(gl, prompt, '\0');
-	gl_echo_mode(gl, saved_echo_mode);
-
-	/* erase prompt */
-	printf("\r%*s\r", (int)strlen(prompt), "");
-
-	return (c);
-}
-
 struct pager *
-pager_create(FILE *fp)
+pager_create(int fd)
 {
 	struct pager	*pager;
 
 	pager = xmalloc(sizeof (struct pager));
 	TAILQ_INIT(&pager->lines_head);
-	pager->fp = fp;
+	pager->fd = fd;
 	pager->buf_size = BUFSIZ;
 	pager->buf = xmalloc(BUFSIZ);
 
@@ -183,9 +164,10 @@
 	return (len);
 }
 
-static unsigned int
-mbsnprint(unsigned int cols, const char *s, FILE *fp)
+static int
+mbsnprint(unsigned int cols, const char *s, int fd)
 {
+	int		retval;
 	const char	*p = s;
 	unsigned int	col = 0;
 	int		mb_len;
@@ -232,21 +214,22 @@
 		p += mb_len;
 		col += width;
 		if (col <= cols) {
-			if (fputs(mb_buf, fp) == EOF) {
-				err(1, "fputs");
+			retval = io_dputs(fd, mb_buf);
+			if (retval != IO_OK) {
+				return (retval);
 			}
 		}
 	}
 
-	fputc('\n', fp);
-	fflush(fp);
+	retval = io_dputs(fd, "\n");
 
-	return (col);
+	return (retval);
 }
 
-void
+enum io_status
 pager_show(struct pager *pager)
 {
+	int		retval = IO_OK;
 	int		is_interactive;
 	unsigned int	rows = 24;
 	unsigned int	cols = 80;
@@ -254,40 +237,45 @@
 	struct winsize	ws;
 #endif /* TIOCGWINSZ */
 	unsigned int	row = 0;
-	GetLine		*gl;
 	struct lines_entry *entry;
+	int		c;
 
-	is_interactive = (isatty(STDIN_FILENO) && (pager->fp == stdout));
+	is_interactive = (isatty(STDIN_FILENO) && (pager->fd == STDOUT_FILENO));
 
 #ifdef	TIOCGWINSZ
 	if (is_interactive) {
 		/* determine terminal size */
-		if (ioctl(fileno(pager->fp), TIOCGWINSZ, &ws) == 0) {
+		if (ioctl(pager->fd, TIOCGWINSZ, &ws) == 0) {
 			rows = (ws.ws_row > 0) ? ws.ws_row : rows;
 			cols = (ws.ws_col > 0) ? ws.ws_col : cols;
 		}
 	}
 #endif /* TIOCGWINSZ */
 
-	gl = new_GetLine(10, 0);
-	if (gl == NULL) {
-		err(1, "new_GetLine");
-	}
-
 	TAILQ_FOREACH(entry, &pager->lines_head, entry) {
 		if (is_interactive) {
-			mbsnprint(cols, entry->line, pager->fp);
+			retval = mbsnprint(cols, entry->line, pager->fd);
+			if (retval != IO_OK) {
+				goto out;
+			}
 			row++;
 			if ((TAILQ_NEXT(entry, entry) != NULL) &&
 			    (row == rows - 1)) {
-				getch_prompt(gl, "--More--");
+				/* prompt for keypress */
+				retval = io_get_char("--More--", &c);
+				if (retval != IO_OK) {
+					goto out;
+				}
 				row = 0;
 			}
 		} else {
-			fprintf(pager->fp, "%s", entry->line);
-			fflush(pager->fp);
+			retval = io_dputs(pager->fd, entry->line);
+			if (retval != IO_OK) {
+				goto out;
+			}
 		}
 	}
 
-	del_GetLine(gl);
+out:
+	return (retval);
 }
--- a/pager.h	Thu Aug 24 13:10:56 2017 +0200
+++ b/pager.h	Fri Sep 01 22:33:41 2017 +0200
@@ -27,12 +27,14 @@
 
 #include <stdarg.h>
 
+#include "io.h"
+
 struct pager;
 
-struct pager *	pager_create(FILE *fp);
+struct pager *	pager_create(int);
 void		pager_destroy(struct pager *);
 int		pager_vprintf(struct pager *, const char *, va_list);
 int		pager_printf(struct pager *, const char *, ...);
-void		pager_show(struct pager *);
+enum io_status	pager_show(struct pager *);
 
 #endif /* !PAGER_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/proc.c	Fri Sep 01 22:33:41 2017 +0200
@@ -0,0 +1,161 @@
+/*
+ * 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 <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "proc.h"
+#include "pwm.h"
+
+#define	PIPE_R	0
+#define	PIPE_W	1
+
+static sigjmp_buf	signal_env;
+
+static void
+signal_handler(int signal_no)
+{
+	siglongjmp(signal_env, signal_no);
+}
+
+enum io_status
+proc_open(struct proc *proc, const char *command, const char *type)
+{
+	int	pipe_fds[2];
+	pid_t	pid;
+
+	if ((strlen(type) != 1) || ((*type != 'r') && (*type != 'w'))) {
+		return (IO_ERROR);
+	}
+
+	if (pipe(pipe_fds) < 0) {
+		return (IO_ERROR);
+	}
+
+	switch (pid = fork()) {
+	case -1:
+		return (IO_ERROR);
+	case 0:
+		if (*type == 'r') {
+			close(pipe_fds[PIPE_R]);
+			dup2(pipe_fds[PIPE_W], STDOUT_FILENO);
+		} else {
+			close(pipe_fds[PIPE_W]);
+			dup2(pipe_fds[PIPE_R], STDIN_FILENO);
+		}
+		closefrom(STDERR_FILENO + 1);
+		execlp("sh", "sh", "-c", command, (char *)0);
+		err(1, "execlp");
+	default:
+		if (*type == 'r') {
+			close(pipe_fds[PIPE_W]);
+			proc->fd = pipe_fds[PIPE_R];
+		} else {
+			close(pipe_fds[PIPE_R]);
+			proc->fd = pipe_fds[PIPE_W];
+		}
+		proc->pid = pid;
+		return (IO_OK);
+	}
+}
+
+enum io_status
+proc_close(struct proc *proc)
+{
+	struct sigaction action;
+	struct sigaction oaction;
+	int		signal_no = 0;
+	pid_t		wpid;
+	int		status;
+
+	close(proc->fd);
+
+	/* 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, send SIGTERM
+		 * to the child process, wait 500 ms and send a SIGKILL if the
+		 * child still exists
+		 */
+		kill(proc->pid, SIGTERM);
+		nanosleep(&(struct timespec){ .tv_nsec = 500 * 1000 * 1000 },
+		    NULL);
+		do {
+			wpid = waitpid(proc->pid, &status, WNOHANG);
+		} while ((wpid == -1) && (errno == EINTR));
+		if (wpid == proc->pid) {
+			goto out;
+		}
+
+		kill(proc->pid, SIGKILL);
+		do {
+			wpid = waitpid(proc->pid, &status, 0);
+		} while ((wpid == -1) && (errno == EINTR));
+
+		goto out;
+	}
+
+	pwm_unblock_signals();
+	do {
+		wpid = waitpid(proc->pid, &status, 0);
+	} while ((wpid == -1) && (errno == EINTR));
+	pwm_block_signals();
+
+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");
+	}
+
+	proc->fd = -1;
+	proc->pid = (pid_t)-1;
+
+	return ((signal_no == 0) ? IO_OK : IO_SIGNAL);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/proc.h	Fri Sep 01 22:33:41 2017 +0200
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef	PROC_H
+#define	PROC_H
+
+#include "io.h"
+
+struct proc {
+	int	fd;
+	pid_t	pid;
+};
+
+enum io_status	proc_open(struct proc *, const char *, const char *);
+enum io_status	proc_close(struct proc *);
+
+#endif /* !PROC_H */
--- a/pwm.c	Thu Aug 24 13:10:56 2017 +0200
+++ b/pwm.c	Fri Sep 01 22:33:41 2017 +0200
@@ -41,22 +41,11 @@
 
 #include "pwm.h"
 #include "cmd.h"
+#include "io.h"
 #include "pwfile.h"
 #include "tok.h"
 #include "util.h"
 
-#ifndef	PWM_HISTORY_ENTRIES_MAX
-#define	PWM_HISTORY_ENTRIES_MAX	1024
-#endif /* !PWM_HISTORY_MAX */
-
-#ifndef	PWM_HISTORY_LINES_MAX
-#define	PWM_HISTORY_LINES_MAX	256
-#endif /* !PWM_HISTORY_LINES_MAX */
-
-#ifndef	PWM_HISTORY_MAX
-#define	PWM_HISTORY_MAX	(PWM_HISTORY_LINES_MAX * PWM_LINE_MAX)
-#endif /* !PWM_HISTORY_MAX */
-
 static void
 usage(void)
 {
@@ -81,11 +70,34 @@
 	}
 }
 
-static int
-complete_nothing(WordCompletion *cpl, void *data, const char *line,
-    int word_end)
+void
+pwm_block_signals(void)
 {
-	return (0);
+	sigset_t	set;
+
+	sigemptyset(&set);
+	sigaddset(&set, SIGINT);
+	sigaddset(&set, SIGTERM);
+	sigaddset(&set, SIGHUP);
+	sigaddset(&set, SIGQUIT);
+	if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) {
+		err(1, "sigprocmask");
+	}
+}
+
+void
+pwm_unblock_signals(void)
+{
+	sigset_t	set;
+
+	sigemptyset(&set);
+	sigaddset(&set, SIGINT);
+	sigaddset(&set, SIGTERM);
+	sigaddset(&set, SIGHUP);
+	sigaddset(&set, SIGQUIT);
+	if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) {
+		err(1, "sigprocmask");
+	}
 }
 
 static int
@@ -94,9 +106,8 @@
 	int		retval = -1;
 	char		prompt[8 + 2 + 1];
 	GetLine		*gl = NULL;
-	char		buf[PWM_LINE_MAX];
-	char		*line = buf;
-	int		c;
+	char		buf[PWM_LINE_MAX + 1];
+	int		io_retval;
 	int		argc = 0;
 	char		**argv = NULL;
 	struct cmd	*cmd;
@@ -104,31 +115,41 @@
 
 	snprintf(prompt, sizeof (prompt), "%.*s> ", 8, getprogname());
 
+	pwm_block_signals();
+
 	/* initialize libtecla */
 	gl = new_GetLine(PWM_LINE_MAX, PWM_HISTORY_MAX);
 	if (gl == NULL) {
 		err(1, "new_GetLine");
 	}
+	gl_catch_blocked(gl);
 	gl_limit_history(gl, PWM_HISTORY_LINES_MAX);
 	/* disable default filename completion */
-	gl_customize_completion(gl, NULL, complete_nothing);
+	gl_customize_completion(gl, NULL, io_gl_complete_nothing);
 
 	for (;;) {
+		/* read next line */
 		cmd = NULL;
-		line = gl_get_line(gl, prompt, NULL, -1);
-		if (line == NULL) {
-			switch (gl_return_status(gl)) {
-			case GLR_EOF:
-				break;
-			case GLR_ERROR:
-				warnx("gl_get_line: %s",
-				    gl_error_message(gl, NULL, 0));
-			}
+		buf[0] = '\0';
+		io_retval = io_get_line(gl, prompt, 1, NULL, 0,
+		    sizeof (buf), buf);
+		switch (io_retval) {
+		case IO_OK:
 			break;
+		case IO_TRUNCATED:
+			/* line was truncated in non-interactive mode */
+			fprintf(stderr, "line too long\n");
+			goto out;
+		case IO_EOF:	/* FALLTHROUGH */
+		case IO_SIGNAL:
+			goto quit;
+		default:
+			fprintf(stderr, "unknown error\n");
+			goto quit;
 		}
 
 		/* tokenize line */
-		switch (tok_tokenize(line, &argc, &argv)) {
+		switch (tok_tokenize(buf, &argc, &argv)) {
 		case TOK_ERR_SYSTEM_ERROR:
 			err(1, "tok_tokenize");
 		case TOK_ERR_UNTERMINATED_QUOTE:
@@ -171,7 +192,9 @@
 				goto out;
 			}
 			break;
-		case CMD_QUIT:
+		case CMD_SIGNAL:
+			fprintf(stderr, "received signal, quitting\n");
+		case CMD_QUIT:	/* FALLTHROUGH */
 			goto quit;
 		}
 		ctx->prev_cmd = cmd->full_cmd;
@@ -201,69 +224,22 @@
 int
 pwm_read_password(struct pwm_ctx *ctx, int is_new_password)
 {
-	int	retval = -1;
-	GetLine	*gl = NULL;
-	char	*line;
-	size_t	len;
-	char	password_buf[sizeof (ctx->password)] = { '\0' };
-
-	/* initialize libtecla */
-	gl = new_GetLine(sizeof (password_buf) - 1, 0);
-	if (gl == NULL) {
-		err(1, "new_GetLine");
-	}
-	/* disable default filename completion */
-	gl_customize_completion(gl, NULL, complete_nothing);
-	gl_echo_mode(gl, 0);
-
-	line = gl_get_line(gl, is_new_password ? "New password: " :
-	    "Password: ", NULL, -1);
-	putchar('\n');
-	if (line == NULL) {
-		if (gl_return_status(gl) == GLR_ERROR) {
-			errx(1, "gl_get_line: %s", gl_error_message(gl, NULL,
-			    0));
-		}
-		goto out;
-	}
-	len = strlen(line);
-	if ((len > 0) && (line[len - 1] == '\n')) {
-		line[--len] = '\0';
+	switch (io_get_password(is_new_password ? "New Password:" :
+	    "Password:", is_new_password ? "Confirm Password:" : NULL,
+	    sizeof (ctx->password), ctx->password)) {
+	case IO_OK:
+		return (0);
+	case IO_SIGNAL:
+		return (-2);
+	case IO_PASSWORD_EMPTY:
+		pwm_err(ctx, "password must not be empty");
+		return (-1);
+	case IO_PASSWORD_MISMATCH:
+		pwm_err(ctx, "passwords do not match");
+		return (-1);
+	default:
+		return (-1);
 	}
-	if (len == 0) {
-		fprintf(stderr, "password must not be empty\n");
-		goto out;
-	}
-	strcpy(password_buf, line);
-
-	/* confirm the entered password entered */
-	if (is_new_password) {
-		line = gl_get_line(gl, "Confirm password: ", NULL, -1);
-		putchar('\n');
-		if (line == NULL) {
-			if (gl_return_status(gl) == GLR_ERROR) {
-				errx(1, "gl_get_line: %s", gl_error_message(gl,
-				    NULL, 0));
-			}
-			goto out;
-		}
-		len = strlen(line);
-		if ((len > 0) && (line[len - 1] == '\n')) {
-			line[--len] = '\0';
-		}
-		if (strcmp(password_buf, line) != 0) {
-			fprintf(stderr, "passwords do not match\n");
-			goto out;
-		}
-	}
-
-	strcpy(ctx->password, password_buf);
-	retval = 0;
-
-out:
-	del_GetLine(gl);
-
-	return (retval);
 }
 
 static int
--- a/pwm.h	Thu Aug 24 13:10:56 2017 +0200
+++ b/pwm.h	Fri Sep 01 22:33:41 2017 +0200
@@ -31,6 +31,18 @@
 #define	PWM_LINE_MAX	16384
 #endif /* !PWM_LINE_MAX */
 
+#ifndef	PWM_HISTORY_ENTRIES_MAX
+#define	PWM_HISTORY_ENTRIES_MAX	1024
+#endif /* !PWM_HISTORY_MAX */
+
+#ifndef	PWM_HISTORY_LINES_MAX
+#define	PWM_HISTORY_LINES_MAX	256
+#endif /* !PWM_HISTORY_LINES_MAX */
+
+#ifndef	PWM_HISTORY_MAX
+#define	PWM_HISTORY_MAX	(PWM_HISTORY_LINES_MAX * PWM_LINE_MAX)
+#endif /* !PWM_HISTORY_MAX */
+
 struct pwm_ctx {
 	const char	*prev_cmd;
 	char		*errmsg;
@@ -43,6 +55,8 @@
 };
 
 void	pwm_err(struct pwm_ctx *, char *, ...);
+void	pwm_block_signals(void);
+void	pwm_unblock_signals(void);
 int	pwm_read_password(struct pwm_ctx *, int);
 
 #endif /* PWM_H */