Mercurial > projects > pwm
changeset 17:a08ef0674d8e
Page long output in interactive mode
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Sat, 12 Aug 2017 10:41:52 +0200 |
parents | a07665727c19 |
children | 1e39a251cbe9 |
files | Makefile cmd.c compat.h compat/queue.h pager.c pager.h pwm.1.xml |
diffstat | 7 files changed, 1041 insertions(+), 54 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Tue Aug 08 10:47:04 2017 +0200 +++ b/Makefile Sat Aug 12 10:41:52 2017 +0200 @@ -81,6 +81,7 @@ HAVE_ASPRINTF ?= 1 HAVE_ERR_H ?= 1 HAVE_GETRANDOM ?= 0 + HAVE_SYS_QUEUE_H ?= 0 HAVE_READPASSPHRASE_H ?= 0 HAVE_SETPROGNAME ?= 0 HAVE_SYS_TREE_H ?= 0 @@ -89,6 +90,7 @@ HAVE_ASPRINTF ?= 1 HAVE_ERR_H ?= 1 HAVE_GETRANDOM ?= 0 + HAVE_SYS_QUEUE_H ?= 1 HAVE_READPASSPHRASE_H ?= 1 HAVE_SETPROGNAME ?= 1 HAVE_SYS_TREE_H ?= 1 @@ -97,6 +99,7 @@ HAVE_ASPRINTF ?= 1 HAVE_ERR_H ?= 1 HAVE_GETRANDOM ?= 0 + HAVE_SYS_QUEUE_H ?= 1 HAVE_READPASSPHRASE_H ?= 0 HAVE_SYS_TREE_H ?= 1 HAVE_SETPROGNAME ?= 1 @@ -105,6 +108,7 @@ HAVE_ASPRINTF ?= 1 HAVE_ERR_H ?= 1 HAVE_GETRANDOM ?= 0 + HAVE_SYS_QUEUE_H ?= 1 HAVE_READPASSPHRASE_H ?= 1 HAVE_SYS_TREE_H ?= 1 HAVE_SETPROGNAME ?= 1 @@ -120,6 +124,7 @@ HAVE_ERR_H ?= 1 HAVE_GETRANDOM ?= 1 endif + HAVE_SYS_QUEUE_H ?= 0 HAVE_READPASSPHRASE_H ?= 0 HAVE_SYS_TREE_H ?= 0 HAVE_SETPROGNAME ?= 0 @@ -128,12 +133,14 @@ HAVE_ASPRINTF ?= 0 HAVE_ERR_H ?= 0 HAVE_GETRANDOM ?= 0 + HAVE_SYS_QUEUE_H ?= 0 HAVE_READPASSPHRASE_H ?= 0 HAVE_SETPROGNAME ?= 0 HAVE_SYS_TREE_H ?= 0 endif OBJS = cmd.o \ + pager.o \ pw.o \ pwfile.o \ pwm.o \
--- a/cmd.c Tue Aug 08 10:47:04 2017 +0200 +++ b/cmd.c Sat Aug 12 10:41:52 2017 +0200 @@ -39,6 +39,7 @@ #include <unistd.h> #include "cmd.h" +#include "pager.h" #include "pw.h" #include "pwfile.h" #include "util.h" @@ -236,6 +237,7 @@ cmd_info(struct pwm_ctx *ctx, int argc, char *argv[]) { struct metadata *metadata; + struct pager *pager; struct tm *tm; char timebuf[TIME_SIZE]; @@ -244,19 +246,23 @@ } metadata = pwfile_get_metadata(ctx); - printf("Format: 0x%04x\n", metadata->version); + + pager = pager_create(stdout); + pager_printf(pager, "Format: 0x%04x\n", metadata->version); if (metadata->user != NULL) { - printf("User: %s\n", metadata->user); + pager_printf(pager, "User: %s\n", metadata->user); } if (metadata->user != NULL) { - printf("Host: %s\n", metadata->host); + pager_printf(pager, "Host: %s\n", metadata->host); } if (metadata->user != NULL) { - printf("Application: %s\n", metadata->application); + pager_printf(pager, "Application: %s\n", metadata->application); } tm = gmtime(&metadata->timestamp); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); - printf("Last Saved: %s\n", timebuf); + pager_printf(pager, "Last Saved: %s\n", timebuf); + pager_show(pager); + pager_destroy(pager); pwfile_destroy_metadata(metadata); @@ -279,6 +285,7 @@ int errcode; char *errbuf; size_t errbuf_size; + struct pager *pager = NULL; union list_item **list = NULL; size_t j; struct record *record; @@ -334,10 +341,11 @@ } } + pager = pager_create(stdout); list = pwfile_create_list(ctx); for (j = 0; list[j] != NULL; j++) { if (list[j]->any.type == ITEM_TYPE_GROUP) { - printf("[%s]\n", list[j]->group.group); + pager_printf(pager, "[%s]\n", list[j]->group.group); } else { record = pwfile_get_record(ctx, list[j]->record.id); if (((group_re == NULL) || (regexec(group_re, @@ -350,16 +358,21 @@ record->notes, 0, NULL, 0) == 0)) && ((url_re == NULL) || (regexec(url_re, record->url, 0, NULL, 0) == 0))) { - printf("%4u %s\n", list[j]->record.id, + pager_printf(pager, "%4u %s\n", + list[j]->record.id, (list[j]->record.title != NULL) ? list[j]->record.title : ""); } pwfile_destroy_record(record); } } + pager_show(pager); + retval = CMD_OK; out: + pager_destroy(pager); + if (group_re != NULL) { regfree(group_re); free(group_re); @@ -383,6 +396,7 @@ pwfile_destroy_list(list); + return (retval); } @@ -656,76 +670,58 @@ return (CMD_OK); } -static int -print_field(const char *label, const char *value, int show_label, FILE *fp) -{ - fprintf(fp, "%s%s\n", show_label ? label : "", (value != NULL) ? - value : ""); - if (ferror(fp)) { - warn("fprintf"); - return (-1); - } - return (0); -} - static void print_record(struct record *record, int fields[], int show_labels, FILE *fp) { + struct pager *pager; struct tm *tm; char timebuf[TIME_SIZE]; + pager = pager_create(fp); if (fields[FIELD_TITLE]) { - if (print_field(field_labels[FIELD_TITLE], record->title, - show_labels, fp) != 0) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_TITLE] : "", (record->title != NULL) ? + record->title : ""); } if (fields[FIELD_GROUP]) { - if (print_field(field_labels[FIELD_GROUP], record->group, - show_labels, fp)) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_GROUP] : "", (record->group != NULL) ? + record->group : ""); } if (fields[FIELD_USERNAME]) { - if (print_field(field_labels[FIELD_USERNAME], record->username, - show_labels, fp)) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_USERNAME] : "", + (record->username != NULL) ? record->username : ""); } if (fields[FIELD_PASSWORD]) { - if (print_field(field_labels[FIELD_PASSWORD], record->password, - show_labels, fp)) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_PASSWORD] : "", + (record->password != NULL) ? record->password : ""); } if (fields[FIELD_NOTES]) { - if (print_field(field_labels[FIELD_NOTES], record->notes, - show_labels, fp)) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_NOTES] : "", (record->notes != NULL) ? + record->notes : ""); } if (fields[FIELD_URL]) { - if (print_field(field_labels[FIELD_URL], record->url, - show_labels, fp)) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_URL] : "", (record->url != NULL) ? + record->url : ""); } if (fields[FIELD_CTIME]) { tm = gmtime(&record->ctime); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); - if (print_field(field_labels[FIELD_CTIME], timebuf, - show_labels, fp)) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_CTIME] : "", timebuf); } if (fields[FIELD_MTIME]) { tm = gmtime(&record->mtime); strftime(timebuf, sizeof (timebuf), TIME_FORMAT, tm); - if (print_field(field_labels[FIELD_MTIME], timebuf, - show_labels, fp)) { - return; - } + pager_printf(pager, "%s%s\n", show_labels ? + field_labels[FIELD_MTIME] : "", timebuf); } + pager_show(pager); + pager_destroy(pager); } static enum cmd_return @@ -909,27 +905,31 @@ static enum cmd_return cmd_help(struct pwm_ctx *ctx, int argc, char *argv[]) { + struct pager *pager; struct cmd *cmd; if (argc > 2) { return (CMD_USAGE); } + pager = pager_create(stdout); if (argc == 2) { for (cmd = cmds; cmd->cmd_func != NULL; cmd++) { if ((strcmp(argv[1], cmd->abbrev_cmd) == 0) || (strcmp(argv[1], cmd->full_cmd) == 0)) { - printf("%s\n", cmd->usage); + pager_printf(pager, "%s\n", cmd->usage); break; } } } else { printf("Commands:\n"); for (cmd = cmds; cmd->cmd_func != NULL; cmd++) { - printf("%-2s %-16s %s\n", cmd->abbrev_cmd, + pager_printf(pager, "%-2s %-16s %s\n", cmd->abbrev_cmd, cmd->full_cmd, cmd->description); } } + pager_show(pager); + pager_destroy(pager); return (CMD_OK); }
--- a/compat.h Tue Aug 08 10:47:04 2017 +0200 +++ b/compat.h Sat Aug 12 10:41:52 2017 +0200 @@ -39,6 +39,10 @@ #include "compat/getentropy.h" #endif /* !HAVE_GETENTROPY */ +#ifndef HAVE_SYS_QUEUE_H +#include "compat/queue.h" +#endif /* !HAVE_SYS_QUEUE_H */ + #ifndef HAVE_READPASSPHRASE_H #include "compat/readpassphrase.h" #endif /* !HAVE_READPASSPHRASE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/compat/queue.h Sat Aug 12 10:41:52 2017 +0200 @@ -0,0 +1,535 @@ +/* $OpenBSD: queue.h,v 1.44 2016/09/09 20:31:46 millert Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +#include <stddef.h> + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues and XOR simple queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * An XOR simple queue is used in the same way as a regular simple queue. + * The difference is that the head structure also includes a "cookie" that + * is XOR'd with the queue pointer (first, last or next) to generate the + * real pointer value. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST(head); \ + (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ + _Q_INVALIDATE((elm)->field.sle_next); \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods. + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST(head); \ + (var) && ((tvar) = LIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SIMPLEQ_FIRST(head); \ + (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_CONCAT(head1, head2) do { \ + if (!SIMPLEQ_EMPTY((head2))) { \ + *(head1)->sqh_last = (head2)->sqh_first; \ + (head1)->sqh_last = (head2)->sqh_last; \ + SIMPLEQ_INIT((head2)); \ + } \ +} while (0) + +/* + * XOR Simple queue definitions. + */ +#define XSIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqx_first; /* first element */ \ + struct type **sqx_last; /* addr of last next element */ \ + unsigned long sqx_cookie; \ +} + +#define XSIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqx_next; /* next element */ \ +} + +/* + * XOR Simple queue access methods. + */ +#define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \ + (unsigned long)(ptr))) +#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) +#define XSIMPLEQ_END(head) NULL +#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) +#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) + + +#define XSIMPLEQ_FOREACH(var, head, field) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) != XSIMPLEQ_END(head); \ + (var) = XSIMPLEQ_NEXT(head, var, field)) + +#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ + (var) = (tvar)) + +/* + * XOR Simple queue functions. + */ +#define XSIMPLEQ_INIT(head) do { \ + arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqx_next = (head)->sqx_first) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ + *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + +#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \ + XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ +} while (0) + +#define XSIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqx_first = XSIMPLEQ_XOR(head, \ + (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ +} while (0) + +#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \ + (elm)->field.sqx_next)->field.sqx_next) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ +} while (0) + + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * Tail queue access methods. + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_NEXT(var, field), 1); \ + (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pager.c Sat Aug 12 10:41:52 2017 +0200 @@ -0,0 +1,400 @@ +/* + * 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 <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_QUEUE_H +#include <sys/queue.h> +#endif /* HAVE_SYS_QUEUE_H */ +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#include <wchar.h> + +#include "pwm.h" +#include "pager.h" +#include "util.h" + +#ifndef TCSASOFT +#define TCSASOFT 0 +#endif /* !TCSASOFT */ + +#ifndef _NSIG +#ifdef NSIG +#define _NSIG NSIG +#else /* NSIG */ +#define _NSIG 128 +#endif /* NSIG */ +#endif /* !_NSIG */ + +#ifndef _PATH_TTY +#define _PATH_TTY "/dev/tty" +#endif + +struct pager { + TAILQ_HEAD(lines_head, lines_entry) lines_head; + FILE *fp; + size_t buf_size; + char *buf; +}; + +struct lines_entry { + TAILQ_ENTRY(lines_entry) entry; + char *line; +}; + +static volatile sig_atomic_t signals[_NSIG]; + +static void +sig_handler(int signo) +{ + signals[signo] = 1; +} + +static int +getch_prompt(const char *prompt) +{ + int retval = -1; + int signo; + int fd; + struct termios saved_term; + struct termios term; + struct sigaction sa; + struct sigaction saved_sa_hup; + struct sigaction saved_sa_int; + struct sigaction saved_sa_quit; + struct sigaction saved_sa_pipe; + struct sigaction saved_sa_alrm; + struct sigaction saved_sa_term; + struct sigaction saved_sa_tstp; + struct sigaction saved_sa_ttin; + struct sigaction saved_sa_ttou; + char c; + int need_restart = 0; + + printf("%s", prompt); + fflush(stdout); + +restart: + for (signo = 0; signo < _NSIG; signo++) { + signals[signo] = 0; + } + + fd = open(_PATH_TTY, O_RDONLY); + if (fd < 0) { + return (-1); + } + + if (tcgetattr(fd, &saved_term) != 0) { + close(fd); + return (-1); + } + memcpy(&term, &saved_term, sizeof (struct termios)); + + /* enter raw mode and turn echo off */ + term.c_lflag &= ~(ICANON | ECHO); + while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) < 0) && + (errno != EINTR)) { + continue; + } + + /* install temporary signal handler */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_handler; + sigaction(SIGHUP, &sa, &saved_sa_hup); + sigaction(SIGINT, &sa, &saved_sa_int); + sigaction(SIGQUIT, &sa, &saved_sa_quit); + sigaction(SIGPIPE, &sa, &saved_sa_pipe); + sigaction(SIGALRM, &sa, &saved_sa_alrm); + sigaction(SIGTERM, &sa, &saved_sa_term); + sigaction(SIGTSTP, &sa, &saved_sa_tstp); + sigaction(SIGTTIN, &sa, &saved_sa_ttin); + sigaction(SIGTTOU, &sa, &saved_sa_ttou); + + /* read key */ + if (read(fd, &c, 1) == sizeof (c)) { + retval = c; + } + + /* restore previous mode and echo setting */ + do { + while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT, + &saved_term) < 0) && (errno != EINTR)) { + continue; + } + } while ((tcgetattr(fd, &term) == 0) && + (term.c_lflag != saved_term.c_lflag)); + + /* restore previous signal handlers */ + sigaction(SIGHUP, &saved_sa_hup, &sa); + sigaction(SIGINT, &saved_sa_int, &sa); + sigaction(SIGQUIT, &saved_sa_quit, &sa); + sigaction(SIGPIPE, &saved_sa_pipe, &sa); + sigaction(SIGALRM, &saved_sa_alrm, &sa); + sigaction(SIGTERM, &saved_sa_term, &sa); + sigaction(SIGTSTP, &saved_sa_tstp, &sa); + sigaction(SIGTTIN, &saved_sa_ttin, &sa); + sigaction(SIGTTOU, &saved_sa_ttou, &sa); + + /* erase prompt */ + printf("\r%*s\r", (int)strlen(prompt), ""); + fflush(stdout); + + while ((close(fd) < 0) && (errno != EINTR)) { + continue; + } + + /* re-raise signals received */ + for (signo = 0; signo < _NSIG; signo++) { + if (signals[signo]) { + raise(signo); + if ((signo == SIGTSTP) || (signo == SIGTTIN) || + (signo == SIGTTOU)) { + need_restart = 1; + } + } + } + if (need_restart) { + goto restart; + } + + return (retval); +} + +struct pager * +pager_create(FILE *fp) +{ + struct pager *pager; + + pager = xmalloc(sizeof (struct pager)); + TAILQ_INIT(&pager->lines_head); + pager->fp = fp; + pager->buf_size = BUFSIZ; + pager->buf = xmalloc(BUFSIZ); + + return (pager); +} + +void +pager_destroy(struct pager *pager) +{ + struct lines_entry *entry; + struct lines_entry *entry_tmp; + + if (pager == NULL) { + return; + } + + TAILQ_FOREACH_SAFE(entry, &pager->lines_head, entry, entry_tmp) { + TAILQ_REMOVE(&pager->lines_head, entry, entry); + free(entry->line); + free(entry); + } + free(pager->buf); + free(pager); +} + +int +pager_vprintf(struct pager *pager, const char *fmt, va_list args) +{ + int len; + va_list args_new; + char *p; + size_t line_len; + struct lines_entry *entry; + size_t line_offset; + + va_copy(args_new, args); + + /* format multibyte string in buffer */ + len = vsnprintf(NULL, 0, fmt, args); + if (len < 0) { + err(1, "vsnprintf"); + } + + if (pager->buf_size < (size_t)len + 1) { + pager->buf_size = len + 1; + pager->buf = xrealloc(pager->buf, pager->buf_size); + } + + len = vsnprintf(pager->buf, pager->buf_size, fmt, args_new); + if (len < 0) { + err(1, "vsnprintf"); + } + + /* split buffer into lines */ + for (p = pager->buf; *p != '\0'; p += line_len) { + line_len = strcspn(p, "\n"); + if (p[line_len] == '\n') { + line_len++; + } + + entry = TAILQ_LAST(&pager->lines_head, lines_head); + line_offset = (entry != NULL) ? strlen(entry->line) : 0; + if ((entry != NULL) && (entry->line[line_offset - 1] != '\n')) { + /* + * append to the last line if it doesn't end with a + * newline + */ + entry->line = xrealloc(entry->line, line_offset + + line_len + 1); + memcpy(entry->line + line_offset, p, line_len); + entry->line[line_offset + line_len] = '\0'; + } else { + entry = xmalloc(sizeof (struct lines_entry)); + entry->line = xmalloc(line_len + 1); + memcpy(entry->line, p, line_len); + entry->line[line_len] = '\0'; + TAILQ_INSERT_TAIL(&pager->lines_head, entry, entry); + } + } + + va_end(args_new); + + return (len); +} + +int +pager_printf(struct pager *pager, const char *fmt, ...) +{ + int len; + va_list args; + + va_start(args, fmt); + len = pager_vprintf(pager, fmt, args); + va_end(args); + + return (len); +} + +static unsigned int +mbsnprint(unsigned int cols, const char *s, FILE *fp) +{ + const char *p = s; + unsigned int col = 0; + int mb_len; + wchar_t wc; + int width; + char mb_buf[MB_LEN_MAX]; + + while ((*p != '\n') && (*p != '\0') && (col < cols)) { + mb_len = mbtowc(&wc, p, MB_CUR_MAX); + if (mb_len != -1) { + width = wcwidth(wc); + if (width != -1) { + if (snprintf(mb_buf, sizeof (mb_buf), "%.*s", + mb_len, p) != mb_len) { + err(1, "snprintf"); + } + } else { + /* + * non-printable character, print + * replacement character + */ + width = 1; + if (snprintf(mb_buf, sizeof (mb_buf), + "\357\277\275") != 3) { + err(1, "snprintf"); + } + } + } else { + /* + * decoding failed, reset state and skip one + * byte + */ + mbtowc(NULL, NULL, MB_CUR_MAX); + mb_len = 1; + + /* print replacement character */ + width = 1; + if (snprintf(mb_buf, sizeof (mb_buf), + "\357\277\275") != 3) { + err(1, "snprintf"); + } + } + + p += mb_len; + col += width; + if (col <= cols) { + if (fputs(mb_buf, fp) == EOF) { + err(1, "fputs"); + } + } + } + + fputc('\n', fp); + fflush(fp); + + return (col); +} + +void +pager_show(struct pager *pager) +{ + int is_interactive; + unsigned int rows = 24; + unsigned int cols = 80; +#ifdef TIOCGWINSZ + struct winsize ws; +#endif /* TIOCGWINSZ */ + unsigned int row = 0; + struct lines_entry *entry; + + is_interactive = (isatty(STDIN_FILENO) && (pager->fp == stdout)); + +#ifdef TIOCGWINSZ + if (is_interactive) { + /* determine terminal size */ + if (ioctl(fileno(pager->fp), TIOCGWINSZ, &ws) == 0) { + rows = (ws.ws_row > 0) ? ws.ws_row : rows; + cols = (ws.ws_col > 0) ? ws.ws_col : cols; + } + } +#endif /* TIOCGWINSZ */ + + TAILQ_FOREACH(entry, &pager->lines_head, entry) { + if (is_interactive) { + mbsnprint(cols, entry->line, pager->fp); + row++; + if (row == rows - 1) { + getch_prompt("--More--"); + row = 0; + } + } else { + fprintf(pager->fp, "%s", entry->line); + fflush(pager->fp); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pager.h Sat Aug 12 10:41:52 2017 +0200 @@ -0,0 +1,38 @@ +/* + * 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 PAGER_H +#define PAGER_H + +#include <stdarg.h> + +struct pager; + +struct pager * pager_create(FILE *fp); +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 *); + +#endif /* !PAGER_H */
--- a/pwm.1.xml Tue Aug 08 10:47:04 2017 +0200 +++ b/pwm.1.xml Sat Aug 12 10:41:52 2017 +0200 @@ -34,7 +34,7 @@ <email>guido+pwm@berhoerster.name</email> <personblurb/> </author> - <date>8 August, 2017</date> + <date>12 August, 2017</date> </info> <refmeta> <refentrytitle>pwm</refentrytitle> @@ -85,6 +85,9 @@ more space characters and the field's verbatim content to the standard output stream. Field content may contain newlines, non-printable and/or control characters.</para> + <para>If running in interactive mode, the <command>list</command>, + <command>show</command> and <command>info</command> will display + the results on a page-by-page basis using an internal pager.</para> <para>The <command>pipe</command> prints the verbatim field content to the standard input stream of the given command.</para> <para>Error messages are printed to the standard error stream.</para>